Properties
category: reference
tags:
  - phase-2
  - auth
  - status
last_updated: 2026-03-13

P2-2: Auth Middleware — Summary

Branch: feat/P2-2-auth-middleware (from phase-2) Status: Complete — 32 tests passing, all existing tests unaffected (49 pass)

Deliverables

app/auth/jwt.py — Platform JWT (RS256)

  • PlatformJWT class: create_token() and validate_token()
  • Issuer/audience: wikibot.io
  • Default lifetime: 24 hours
  • Validates algorithm, signature, issuer, audience, expiry

app/auth/workos.py — WorkOS Integration

  • WorkOSClient class wrapping WorkOS User Management API
  • exchange_code(): auth code → tokens + user profile
  • get_user_profile(): fetch user details by WorkOS user ID
  • get_provider_sub(): retrieve raw OAuth provider + subject ID (for migration off WorkOS)
  • get_authorization_url(): build AuthKit redirect URL
  • WorkOSError exception for API failures

app/auth/middleware.py — Auth Middleware

  • AuthMiddleware class with two entry points:
    1. authenticate(authorization_header) — validates Bearer JWT, resolves to AuthenticatedUser dataclass
    2. exchange_auth_code(code) — full login flow: WorkOS code exchange → provider sub lookup → find-or-create DynamoDB user → issue platform JWT
  • AuthenticatedUser dataclass: user_id, email, display_name, tier, record
  • AuthError exception with HTTP status and JSON response helper
  • On first login: creates user in DynamoDB with oauth_provider + oauth_provider_sub
  • On subsequent login: resolves existing user, updates email/display_name if changed
  • Provider sub fallback: if WorkOS identity lookup fails, stores workos as provider

Test Coverage (32 tests)

Test Class Count What it covers
TestPlatformJWT 9 Create/validate, expiry, wrong key/issuer/audience, malformed, empty
TestWorkOSClient 5 Code exchange (success/failure), profile, provider sub, auth URL
TestAuthMiddleware 7 Valid JWT, missing/empty/bad header, expired, invalid, user not found, wrong key
TestAuthCodeExchange 6 New user, existing user, profile update, WorkOS failure, provider sub fallback, no client
TestAuthIntegration 2 Full flow (login → JWT → auth request), second login same user
TestAuthError 1 JSON error response format

Design Decisions

  1. WorkOS not in runtime path — WorkOS is only contacted during login (code exchange). All subsequent requests validate platform JWTs against our own RS256 key.
  2. Raw provider sub stored — Enables migration off WorkOS per Design/Auth.
  3. Provider sub fallback — If WorkOS identity API fails (e.g., Apple sub issue), falls back to workos as provider with WorkOS user ID as sub. Logs a warning.
  4. No WorkOS JWKS validation in this middleware — MCP route WorkOS token validation is handled by FastMCP's WorkOS integration (separate concern, per PRD). This middleware covers platform JWT only.
  5. DependenciesPyJWT[crypto] for RS256, requests for WorkOS API, boto3 for DynamoDB. All available in Lambda runtime.

Acceptance Criteria

  • Valid platform JWT → request proceeds with user context
  • Expired/invalid JWT → 401
  • WorkOS access token validated against JWKS (deferred to FastMCP integration for MCP routes)
  • Auth code exchange returns platform JWT with correct claims
  • User record created in DynamoDB on first login (with oauth_provider, oauth_provider_sub)
  • Subsequent logins resolve to existing user record
  • Unit tests pass (32/32)