-
Notifications
You must be signed in to change notification settings - Fork 0
Tech Story: Switch auth tokens to httpOnly cookies #93
Copy link
Copy link
Open
Labels
apiPublic/internal API endpointsPublic/internal API endpointsbackendBackend services and logicBackend services and logicfrontendFrontend app and dashboardFrontend app and dashboardsecuritySecurity, auth, and permissionsSecurity, auth, and permissionstech-storyTechnical implementation storyTechnical implementation story
Milestone
Description
Tech Story
As a platform engineer, I want access and refresh tokens stored in httpOnly cookies so that they cannot be read or exfiltrated by JavaScript, eliminating the XSS attack surface introduced by localStorage.
Context
Currently both tokens are stored in localStorage and injected manually into Authorization: Bearer headers. Any XSS vector (compromised dependency, injected script) can trivially steal both tokens. Storing tokens in httpOnly; Secure; SameSite=Strict cookies removes this surface entirely — the browser sends them automatically and JS cannot read them.
Acceptance Criteria
-
POST /auth/loginsetsaccess_tokenandrefresh_tokenashttpOnly; Secure; SameSite=Strictcookies; response body returns user info only (no tokens) -
POST /auth/refreshreads the refresh token from its cookie, issues new token pair as cookies -
POST /auth/logoutreads the refresh token from its cookie, revokes it, clears both cookies -
JwtStrategyextracts the access token from theaccess_tokencookie (not Authorization header) -
RefreshTokenStrategyandRefreshTokenAuthGuardremoved — refresh/logout read cookies directly - Frontend
apiClientsendswithCredentials: true; all manual token reads/writes from localStorage removed -
ProtectedRouteusesAuthContext(backed byGET /auth/me) instead oflocalStoragecheck -
GET /auth/meendpoint added — returns{ id, username }for authenticated users, 401 otherwise - Login and Register pages no longer touch localStorage
- Dead
api.service.ts(hardcoded wrong port, unused) deleted - Existing sessions invalidated (acceptable — users must re-login after deploy)
Technical Elaboration
- Use
cookie-parsermiddleware inmain.ts; installcookie-parserand@types/cookie-parser - Cookie config:
httpOnly: true,secure: true(env-gated for local dev),sameSite: 'strict' - Access token cookie expiry matches JWT expiry (15m); refresh token cookie expiry = 7 days
JwtStrategy: changejwtFromRequesttoExtractJwt.fromExtractors([(req) => req?.cookies?.access_token])AuthContext(React): callsGET /auth/meon mount; exposes{ user, loading, logout };ProtectedRoutereads from contextapiClient401 interceptor: callPOST /auth/refreshwithwithCredentials: true; on success retry original request; on failure redirect to /login- CORS must have
credentials: trueand explicitorigin(wildcard*is incompatible with credentialed cookies)
Notes
- Cookie
secureflag should beNODE_ENV === 'production'so local dev over HTTP still works - This issue is a prerequisite for or should be done in conjunction with the CORS/Helmet hardening issue
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
apiPublic/internal API endpointsPublic/internal API endpointsbackendBackend services and logicBackend services and logicfrontendFrontend app and dashboardFrontend app and dashboardsecuritySecurity, auth, and permissionsSecurity, auth, and permissionstech-storyTechnical implementation storyTechnical implementation story