Blame

905ba1 Claude (Dev) 2026-03-13 06:33:13
[mcp] Add P2-2 auth middleware summary
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)