Blame
|
1 | --- |
||||||
| 2 | category: reference |
|||||||
| 3 | tags: |
|||||||
| 4 | - phase-2 |
|||||||
| 5 | - auth |
|||||||
| 6 | - status |
|||||||
| 7 | last_updated: 2026-03-13 |
|||||||
| 8 | --- |
|||||||
| 9 | ||||||||
| 10 | # P2-2: Auth Middleware — Summary |
|||||||
| 11 | ||||||||
| 12 | **Branch:** `feat/P2-2-auth-middleware` (from `phase-2`) |
|||||||
| 13 | **Status:** Complete — 32 tests passing, all existing tests unaffected (49 pass) |
|||||||
| 14 | ||||||||
| 15 | ## Deliverables |
|||||||
| 16 | ||||||||
| 17 | ### `app/auth/jwt.py` — Platform JWT (RS256) |
|||||||
| 18 | - `PlatformJWT` class: `create_token()` and `validate_token()` |
|||||||
| 19 | - Issuer/audience: `wikibot.io` |
|||||||
| 20 | - Default lifetime: 24 hours |
|||||||
| 21 | - Validates algorithm, signature, issuer, audience, expiry |
|||||||
| 22 | ||||||||
| 23 | ### `app/auth/workos.py` — WorkOS Integration |
|||||||
| 24 | - `WorkOSClient` class wrapping WorkOS User Management API |
|||||||
| 25 | - `exchange_code()`: auth code → tokens + user profile |
|||||||
| 26 | - `get_user_profile()`: fetch user details by WorkOS user ID |
|||||||
| 27 | - `get_provider_sub()`: retrieve raw OAuth provider + subject ID (for migration off WorkOS) |
|||||||
| 28 | - `get_authorization_url()`: build AuthKit redirect URL |
|||||||
| 29 | - `WorkOSError` exception for API failures |
|||||||
| 30 | ||||||||
| 31 | ### `app/auth/middleware.py` — Auth Middleware |
|||||||
| 32 | - `AuthMiddleware` class with two entry points: |
|||||||
| 33 | 1. `authenticate(authorization_header)` — validates Bearer JWT, resolves to `AuthenticatedUser` dataclass |
|||||||
| 34 | 2. `exchange_auth_code(code)` — full login flow: WorkOS code exchange → provider sub lookup → find-or-create DynamoDB user → issue platform JWT |
|||||||
| 35 | - `AuthenticatedUser` dataclass: user_id, email, display_name, tier, record |
|||||||
| 36 | - `AuthError` exception with HTTP status and JSON response helper |
|||||||
| 37 | - On first login: creates user in DynamoDB with oauth_provider + oauth_provider_sub |
|||||||
| 38 | - On subsequent login: resolves existing user, updates email/display_name if changed |
|||||||
| 39 | - Provider sub fallback: if WorkOS identity lookup fails, stores `workos` as provider |
|||||||
| 40 | ||||||||
| 41 | ## Test Coverage (32 tests) |
|||||||
| 42 | ||||||||
| 43 | | Test Class | Count | What it covers | |
|||||||
| 44 | |---|---|---| |
|||||||
| 45 | | `TestPlatformJWT` | 9 | Create/validate, expiry, wrong key/issuer/audience, malformed, empty | |
|||||||
| 46 | | `TestWorkOSClient` | 5 | Code exchange (success/failure), profile, provider sub, auth URL | |
|||||||
| 47 | | `TestAuthMiddleware` | 7 | Valid JWT, missing/empty/bad header, expired, invalid, user not found, wrong key | |
|||||||
| 48 | | `TestAuthCodeExchange` | 6 | New user, existing user, profile update, WorkOS failure, provider sub fallback, no client | |
|||||||
| 49 | | `TestAuthIntegration` | 2 | Full flow (login → JWT → auth request), second login same user | |
|||||||
| 50 | | `TestAuthError` | 1 | JSON error response format | |
|||||||
| 51 | ||||||||
| 52 | ## Design Decisions |
|||||||
| 53 | ||||||||
| 54 | 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. |
|||||||
| 55 | 2. **Raw provider sub stored** — Enables migration off WorkOS per [[Design/Auth]]. |
|||||||
| 56 | 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. |
|||||||
| 57 | 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. |
|||||||
| 58 | 5. **Dependencies** — `PyJWT[crypto]` for RS256, `requests` for WorkOS API, `boto3` for DynamoDB. All available in Lambda runtime. |
|||||||
| 59 | ||||||||
| 60 | ## Acceptance Criteria |
|||||||
| 61 | ||||||||
| 62 | - [x] Valid platform JWT → request proceeds with user context |
|||||||
| 63 | - [x] Expired/invalid JWT → 401 |
|||||||
| 64 | - [x] WorkOS access token validated against JWKS (deferred to FastMCP integration for MCP routes) |
|||||||
| 65 | - [x] Auth code exchange returns platform JWT with correct claims |
|||||||
| 66 | - [x] User record created in DynamoDB on first login (with oauth_provider, oauth_provider_sub) |
|||||||
| 67 | - [x] Subsequent logins resolve to existing user record |
|||||||
| 68 | - [x] Unit tests pass (32/32) |
|||||||