Summary
Migrate OAuth token handling from client-side localStorage to a server-side Backend-For-Frontend (BFF) pattern. The App API (FastAPI) will handle the entire OAuth code exchange, store tokens server-side, and issue an HttpOnly session cookie to the browser. The frontend will never see or store OAuth tokens.
This addresses the findings in CODE_REVIEW_TOKEN_STORAGE.md — specifically F-01 (refresh token in localStorage), F-02 (access token in localStorage), F-03 (no token binding), F-04 (tamperable expiry), F-05 (persistence after logout intent), and F-07 (cross-tab sync gap).
Context
The IETF draft-ietf-oauth-browser-based-apps-26 explicitly recommends the BFF pattern for "business applications, sensitive applications, and applications that handle personal data." This application handles student data at an institution and qualifies.
See CODE_REVIEW_TOKEN_STORAGE.md in the repo root for the full security review, risk analysis, and alternative options considered.
Backend Changes (App API)
New Auth Endpoints
Add the following endpoints to the App API (backend/src/apis/app_api/auth/):
| Endpoint |
Method |
Description |
/auth/login |
GET |
Initiates OAuth flow — generates PKCE challenge, state, and redirects to IdP |
/auth/callback |
GET |
Handles IdP callback — exchanges code for tokens, creates server-side session, sets cookie |
/auth/session |
GET |
Returns current session info (user profile, expiry) — used by frontend to check auth state |
/auth/refresh |
POST |
Refreshes the access token server-side using the stored refresh token |
/auth/logout |
POST |
Clears server-side session and cookie |
Server-Side Session Storage
- Store sessions in DynamoDB (new
sessions-auth table or reuse existing session infrastructure)
- Session record contains:
session_id, user_id, access_token, refresh_token, token_expiry, created_at, last_accessed
- Session ID is a cryptographically random value (not the access token)
- TTL on DynamoDB records for automatic cleanup of abandoned sessions
Cookie Configuration
Set-Cookie: __Host-session=<session_id>; HttpOnly; Secure; SameSite=Strict; Path=/
HttpOnly — not accessible to JavaScript
Secure — only sent over HTTPS
SameSite=Strict — not sent on cross-origin requests
__Host- prefix — requires Secure, no Domain, Path=/
Request Proxying
The App API already serves as the BFF for the Inference API. Extend this pattern so that all authenticated requests include the access token server-side by reading it from the session store, rather than expecting the frontend to attach it.
Frontend Changes
auth.service.ts
- Remove all
localStorage.getItem/setItem calls for tokens
login() redirects to App API /auth/login instead of directly to the IdP
handleCallback() is no longer needed (App API handles the callback)
isAuthenticated() checks session via /auth/session endpoint (or cookie presence)
logout() calls /auth/logout and clears local state
auth.interceptor.ts
- Remove token attachment logic entirely — cookies are sent automatically by the browser
- Handle 401 responses by redirecting to login
auth.guard.ts
- Replace local token inspection with a lightweight
/auth/session call
- Cache the session check result briefly to avoid excessive API calls on route transitions
Infrastructure Changes
- Add
sessions-auth DynamoDB table to Infrastructure stack (if not reusing existing tables)
- Export table name/ARN via SSM
- Grant App API Fargate task role read/write access
- Configure TTL attribute for automatic session expiry cleanup
Acceptance Criteria
References
Summary
Migrate OAuth token handling from client-side
localStorageto a server-side Backend-For-Frontend (BFF) pattern. The App API (FastAPI) will handle the entire OAuth code exchange, store tokens server-side, and issue an HttpOnly session cookie to the browser. The frontend will never see or store OAuth tokens.This addresses the findings in
CODE_REVIEW_TOKEN_STORAGE.md— specifically F-01 (refresh token in localStorage), F-02 (access token in localStorage), F-03 (no token binding), F-04 (tamperable expiry), F-05 (persistence after logout intent), and F-07 (cross-tab sync gap).Context
The IETF draft-ietf-oauth-browser-based-apps-26 explicitly recommends the BFF pattern for "business applications, sensitive applications, and applications that handle personal data." This application handles student data at an institution and qualifies.
See
CODE_REVIEW_TOKEN_STORAGE.mdin the repo root for the full security review, risk analysis, and alternative options considered.Backend Changes (App API)
New Auth Endpoints
Add the following endpoints to the App API (
backend/src/apis/app_api/auth/):/auth/login/auth/callback/auth/session/auth/refresh/auth/logoutServer-Side Session Storage
sessions-authtable or reuse existing session infrastructure)session_id,user_id,access_token,refresh_token,token_expiry,created_at,last_accessedCookie Configuration
HttpOnly— not accessible to JavaScriptSecure— only sent over HTTPSSameSite=Strict— not sent on cross-origin requests__Host-prefix — requires Secure, no Domain, Path=/Request Proxying
The App API already serves as the BFF for the Inference API. Extend this pattern so that all authenticated requests include the access token server-side by reading it from the session store, rather than expecting the frontend to attach it.
Frontend Changes
auth.service.tslocalStorage.getItem/setItemcalls for tokenslogin()redirects to App API/auth/logininstead of directly to the IdPhandleCallback()is no longer needed (App API handles the callback)isAuthenticated()checks session via/auth/sessionendpoint (or cookie presence)logout()calls/auth/logoutand clears local stateauth.interceptor.tsauth.guard.ts/auth/sessioncallInfrastructure Changes
sessions-authDynamoDB table to Infrastructure stack (if not reusing existing tables)Acceptance Criteria
/auth/sessionreturns user profile and auth state without exposing tokensReferences
CODE_REVIEW_TOKEN_STORAGE.md— Full security review with findings and risk analysis