Skip to main content

JWT Middleware Pipeline

Middleware Execution Order

JWT Validation Steps

Step 1: Token Extraction
Step 2: Signature Validation
Validation Failures:
  • Invalid signature → SecurityTokenInvalidSignatureException → 401
  • Wrong issuer → SecurityTokenInvalidIssuerException → 401
  • Wrong audience → SecurityTokenInvalidAudienceException → 401
  • Expired token → SecurityTokenExpiredException → 401
Step 3: Claims Extraction
Step 4: Context Population
Controller accesses via:

User Synchronization

Automatic Sync Trigger

Every authenticated controller action calls:

EnsureUserExistsAsync Implementation

SQL Executed:

Sync Characteristics

Performance: ~5-15ms (indexed UPSERT) Idempotency: Safe to call multiple times Failure Handling: Logged as warning, request continues Timing: Synchronous (awaited before proceeding) Frequency: Every authenticated request

Supabase Integration

JWT Issuer Validation

Supabase issues JWTs with specific issuer format:
Backend validates:
Configuration:
Validation ensures token came from correct Supabase project.

Service Role vs Anon Key

Client-Side (Supabase Anon Key):
Server-Side (Supabase Service Role):
Why Service Role in Backend?
  • Full database access (no RLS restrictions)
  • Perform UPSERT operations
  • Read all tables regardless of RLS policies
  • Backend implements authorization in application code

Authorization vs Authentication

Authentication

Question: “Who are you?” Mechanism: JWT validation via middleware Result: User principal populated with claims Happens: Before controller method execution

Authorization

Question: “Are you allowed to do this?” Mechanism: Custom logic in controller Example:
Happens: Inside controller method logic

[Authorize] Attribute

[AllowAnonymous] Attribute

Public Endpoints:
  • /health
  • /api/health
  • /api/ping
  • /api/settings/feature-flag/{key}
  • /api/speech/generate

Token Lifecycle

Token Acquisition (Client)

Token Usage

Token Expiration

Access Token: Expires after 1 hour Detection:

Token Refresh

Automatic Refresh: Supabase client libraries auto-refresh before expiration

Security Considerations

JWT Secret Protection

Environment Variable:
Acquisition: From Supabase project settings → API → JWT Settings Critical: Never commit to version control Development: If missing, signature validation may be skipped (logged warning) Production: MUST be set or all requests will fail 401

HTTPS Requirement

JWTs transmitted in Authorization header (plaintext over HTTP). Development: HTTP acceptable (localhost) Production: HTTPS mandatory Why: Bearer tokens are susceptible to man-in-the-middle attacks over HTTP

Token Revocation

Limitation: JWTs cannot be revoked before expiration Mitigation: Short expiry (1 hour) Workaround: Track revoked tokens in database (not currently implemented)

Supabase Auth Events

Not Used: DualMind doesn’t listen to Supabase auth webhooks User Deletion: Deleting user in Supabase doesn’t cascade to DualMind database Implication: Orphaned user records possible if user deleted from Supabase Auth Future: Implement webhook listener for user.deleted event

Development vs Production

Development Mode

JWT_SECRET: May be missing (validation skipped with warning) CORS: Allows all origins HTTPS: Not enforced Logging: Verbose request/response logging

Production Mode

JWT_SECRET: MUST be set (validation enforced) CORS: Configured allowed origins only HTTPS: Enforced at infrastructure level Logging: Error-level only (no verbose logging)

Troubleshooting

401 Unauthorized

Causes:
  1. Missing Authorization header
  2. Invalid JWT signature (wrong JWT_SECRET)
  3. Expired token (exp < now)
  4. Wrong issuer (Supabase URL mismatch)
  5. Wrong audience (aud != "authenticated")
Debug:

403 Forbidden

Causes:
  1. Valid JWT but accessing resource owned by different user
  2. Private thread accessed by non-owner
Debug: Check thread.user_id vs JWT sub claim

User Sync Failures

Symptom: Warnings in logs but requests succeed Causes:
  1. Database connectivity issue
  2. Invalid user_id format (not UUID)
  3. Supabase service role key incorrect
Impact: Minimal (user record may be stale but auth works)

Next Steps

Request Lifecycle

Where JWT validation fits in execution flow

System Invariants

Authentication-related invariants