feat(auth): add dormant BFF token-handler backend infra (Phase 2)#213
Merged
Conversation
Lands the server-side plumbing for the Bearer-to-cookie migration without activating it. New `apis/shared/sessions_bff/` package (config, repository, AES-GCM cookie codec backed by KMS GenerateDataKey, double-submit CSRF, TTL cache, per-session refresh lock, Cognito refresh-token client) plus `SessionRefreshMiddleware` and `CSRFMiddleware` registered on app-api. Both middlewares are no-ops until a `__Host-bff_session` cookie is present and `BFFConfig.is_enabled()` returns True, so this is safe to ship before any caller flips on. Also adds `get_current_user_from_session` dependency in `apis/shared/auth/dependencies.py` for Phase 6 router cutover. No router consumes it yet. Includes 46 tests covering codec round-trip + tamper rejection, CSRF validation, repository CRUD with TTL semantics, middleware pass-through when dormant, and a multi-tab refresh-token-storm coalescing test that asserts N concurrent requests for the same session id drive exactly one Cognito refresh exchange. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
||
| import asyncio | ||
| from threading import Lock as _ThreadLock | ||
| from typing import Dict |
| try: | ||
| parsed = json.loads(value) | ||
| value = parsed.get("clientSecret") or parsed.get("client_secret") or value | ||
| except json.JSONDecodeError: |
- Validate session-cookie tokens against COGNITO_BFF_APP_CLIENT_ID via a separate validator instance; the SPA validator's client_id check would reject every BFF-issued token at Phase 6 cutover. - Bind the cookie version byte into AES-GCM associated data and stop swallowing KMS infrastructure errors as decode failures, so a transient KMS hiccup no longer logs every active user out. - Correct the middleware-order comment in app_api/main.py (last-added is outermost) and add a Phase-6 marker on the CORS allow_credentials flag. - TODO markers for the Cognito-rotation/DDB-write race and for the Phase-3 logout cache-invalidation gap. - Tighten the CSRF tokens_match docstring to match actual constant-time semantics. - Test updates: cover KMS-error propagation, assert both BFF cookies are cleared on unrecoverable cookies, and patch the new BFF validator name. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/auth/*routes.apis/shared/sessions_bff/package:BFFConfig(env-driven, gated byis_enabled()),SessionRepository(DDB CRUD with TTL semantics),CookieCodec(KMSGenerateDataKey→ AES-GCM seal of session id), CSRF double-submit helpers, TTL cache, per-sessionasyncio.Lockregistry for refresh-storm coalescing, andCognitoRefreshClient(InitiateAuthREFRESH_TOKEN_AUTH with cached SECRET_HASH).SessionRefreshMiddlewareandCSRFMiddlewareregistered on app-api in main.py. Both are registered unconditionally so a Phase 1 env-var deploy can flip them on without a code redeploy. They short-circuit whenBFFConfig.is_enabled()is False (local dev, environments before Phase 1 deployed) and when no__Host-bff_sessioncookie is present (Bearer-token requests).get_current_user_from_sessiondependency in apis/shared/auth/dependencies.py for Phase 6 router cutover. No router consumes it yet — intentional per the Phase 2 scope.Design choices worth flagging
__Host-bff_session— the prefix forbidsDomain=and requiresSecure+Path=/, giving same-origin-only delivery for free once CloudFront/api/*lands in Phase 6.kms:GenerateDataKeyreturns a 256-bit AES key; plaintext is cached in process memory, wrapped blob discarded. Matches the Phase 1 CDK comment. Key rotation requires a task restart, which is on the Phase 7 hardening list.asyncio.Lockprevents N parallel tab refreshes from tumbling each other's refresh tokens via Cognito rotation. In-process only (Phase 7 will extend to multi-task with a DDB conditional write).Test plan
pytest tests/apis/shared/sessions_bff/ -v— 46 new tests all pass/auth/login|callback|logouthttpx.AsyncClient(ASGITransport)for the same near-expiry session → exactly one Cognito refresh exchangepytest tests/apis/shared/ tests/architecture/— 92/92 pass, no import-boundary violationspytest tests/apis/app_api/ tests/auth/— 102/102 pass, no regressions fromdependencies.pychangespython -c "from apis.app_api import main"— app-api imports cleanly with 4 middlewares registered (CORS → AgentCoreContext → SessionRefresh → CSRF)🤖 Generated with Claude Code