Skip to content

feat(auth): add refresh token session renewal for web and mobile - #143

Merged
HansonL622 merged 10 commits into
1024XEngineer:mainfrom
pionxe:codex/auth-refresh-token
Aug 21, 2026
Merged

feat(auth): add refresh token session renewal for web and mobile#143
HansonL622 merged 10 commits into
1024XEngineer:mainfrom
pionxe:codex/auth-refresh-token

Conversation

@pionxe

@pionxe pionxe commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add persisted refresh-token sessions with rotation and revocation
  • renew short-lived access tokens for the Web learning client and mobile app
  • store Web refresh tokens in HttpOnly session cookies
  • store mobile access and refresh tokens in Expo SecureStore
  • coordinate concurrent refresh requests and retry the original request once

Backend

  • add auth_refresh_tokens table through V7__auth_refresh_tokens.sql
  • store token digests instead of raw refresh tokens
  • enforce 30-minute access-token TTL
  • enforce 7-day idle and 90-day absolute refresh-token TTLs
  • rotate refresh tokens atomically
  • revoke tokens on logout, password changes, account changes, and replay detection
  • add delayed cleanup for expired refresh-token records
  • avoid database foreign keys in accordance with project conventions

Web

  • store access tokens in sessionStorage
  • store refresh tokens in an HttpOnly session cookie
  • refresh once after a protected request returns 401
  • update media requests, realtime session creation, and reconnect flows
  • revoke the Web refresh session on logout

Mobile

  • store access and refresh tokens separately in SecureStore
  • restore the session during app startup
  • refresh and retry protected requests after 401
  • serialize concurrent refresh requests
  • update authenticated media and realtime socket flows
  • revoke and clear both tokens on logout

Validation

  • backend Maven compilation passed
  • Web build passed
  • Web auth checks passed: 22/22
  • Web realtime and route checks passed
  • mobile TypeScript check passed
  • mobile Jest passed: 47 suites, 230 tests
  • verified refresh-token recovery on a connected Android device

Close #140

fennoai[bot]

This comment was marked as outdated.

@pionxe

pionxe commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

/review

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment on lines +55 to +57
} catch (error) {
await this.onRefreshFailure?.(error);
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment on lines +58 to +59
if (current == null || current.revokedAt() != null || !current.expiresAt().isAfter(now)
|| !current.lastUsedAt().plus(idleTtl).isAfter(now)) throw invalid();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

@HansonL622
HansonL622 merged commit 4daefed into 1024XEngineer:main Aug 21, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(Auth): 为 Web 学习端和移动端增加统一 Refresh Token 机制

2 participants