feat(auth): add refresh token session renewal for web and mobile - #143
Conversation
|
/review |
There was a problem hiding this comment.
The refresh-token flow compiles and the existing Web auth contract checks pass, but the deployment migration, reuse handling, and client recovery paths still leave sessions unavailable or prematurely discarded in normal scenarios. I verified the backend with ./mvnw -q -DskipTests compile and the Web with npm run test:auth (22/22); targeted mobile Jest could not run because the workspace's Jest executable is unavailable/non-executable.
| } | ||
|
|
||
| export async function refreshAccessToken() { | ||
| if (!getAccessToken()) return null; |
There was a problem hiding this comment.
[P1] Bootstrap Web sessions from the refresh cookie
Access tokens are now stored in sessionStorage, which is not shared with a newly opened tab, while the HttpOnly refresh cookie is shared for the browser session. This guard prevents the refresh request whenever the new tab has no access token, and App immediately redirects protected routes to login, so a valid refresh session cannot restore authentication in another tab. Allow a bootstrap refresh based on the cookie (the endpoint already validates Origin) before deciding the session is anonymous.
| } catch (error) { | ||
| await this.onRefreshFailure?.(error); | ||
| return null; |
There was a problem hiding this comment.
[P1] Preserve mobile refresh tokens on transient failures
This catch handles every refresh failure identically, and the production configuration wires onRefreshFailure to authTokenCoordinator.clear(). A timeout, offline startup, or server 5xx therefore deletes both SecureStore tokens even though the refresh token may still be valid, forcing the user to sign in again after a transient outage. Only clear durable credentials for an explicit invalid-refresh response (for example 400/401 with REFRESH_TOKEN_INVALID); propagate or retain the token for network and 5xx failures.
| if (current == null || current.revokedAt() != null || !current.expiresAt().isAfter(now) | ||
| || !current.lastUsedAt().plus(idleTtl).isAfter(now)) throw invalid(); |
There was a problem hiding this comment.
[P1] Revoke the rotated session when a token is replayed
After one refresh rotates token A to token B, replaying A only hits this invalid-token branch (or the failed consume) and leaves B active. That means theft/reuse of an already-consumed refresh token is detected but does not revoke the compromised session, despite the PR's replay-revocation contract. Track the token family (or otherwise identify the user before rejecting) and revoke its active descendants/all refresh tokens when reuse is observed.
Summary
Backend
auth_refresh_tokenstable throughV7__auth_refresh_tokens.sqlWeb
sessionStorageMobile
Validation
Close #140