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)
PlatformJWTclass:create_token()andvalidate_token()- Issuer/audience:
wikibot.io - Default lifetime: 24 hours
- Validates algorithm, signature, issuer, audience, expiry
app/auth/workos.py — WorkOS Integration
WorkOSClientclass wrapping WorkOS User Management APIexchange_code(): auth code → tokens + user profileget_user_profile(): fetch user details by WorkOS user IDget_provider_sub(): retrieve raw OAuth provider + subject ID (for migration off WorkOS)get_authorization_url(): build AuthKit redirect URLWorkOSErrorexception for API failures
app/auth/middleware.py — Auth Middleware
AuthMiddlewareclass with two entry points:authenticate(authorization_header)— validates Bearer JWT, resolves toAuthenticatedUserdataclassexchange_auth_code(code)— full login flow: WorkOS code exchange → provider sub lookup → find-or-create DynamoDB user → issue platform JWT
AuthenticatedUserdataclass: user_id, email, display_name, tier, recordAuthErrorexception 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
workosas 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
- WorkOS not in runtime path — WorkOS is only contacted during login (code exchange). All subsequent requests validate platform JWTs against our own RS256 key.
- Raw provider sub stored — Enables migration off WorkOS per Design/Auth.
- Provider sub fallback — If WorkOS identity API fails (e.g., Apple sub issue), falls back to
workosas provider with WorkOS user ID as sub. Logs a warning. - 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.
- Dependencies —
PyJWT[crypto]for RS256,requestsfor WorkOS API,boto3for 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)