{"record":{"id":"16b11d4233dd7dcb","repo":"toeverything/AFFiNE","slug":"access-token-expired","errorCode":"access_token_expired","errorMessage":"The access token has expired.","messagePattern":"The access token has expired\\.","errorType":"exception","errorClass":"AuthSessionHttpError","httpStatus":401,"severity":"warning","filePath":"packages/backend/server/src/core/auth/guard.ts","lineNumber":144,"sourceCode":"    const result = await this.resolveRequestSession(req, res, isPublic);\n    return result?.session ?? null;\n  }\n\n  private async resolveRequestSession(\n    req: Request,\n    res?: Response,\n    isPublic = false\n  ): Promise<AuthenticatedRequestSession | null> {\n    const bearer = req.headers.authorization\n      ? extractTokenFromHeader(req.headers.authorization)\n      : undefined;\n    if (bearer && isLikelyJwt(bearer)) {\n      try {\n        const session = await this.signInWithJwt(req, bearer, res, isPublic);\n        return session ? { type: 'jwt', session } : null;\n      } catch (err) {\n        if (err instanceof SessionAccessTokenError) {\n          throw new AuthSessionHttpError(err.code);\n        }\n        throw err;\n      }\n    }\n\n    const session = await this.signInWithCookie(req, res, isPublic);\n    return session ? { type: 'cookie_session', session } : null;\n  }\n\n  async signInWithJwt(\n    req: Request,\n    token: string,\n    res?: Response,\n    isPublic = false\n  ): Promise<Session | null> {\n    if (req.session && req.authType === 'jwt') return req.session;\n    const session = await this.accessTokens.verify(token);\n    const versionAllowed = await this.checkUserSessionClientVersion(","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/auth/guard.ts#L126-L162","documentation":"Surfaced when `AccessTokenService.verify` throws `SessionAccessTokenError('ACCESS_TOKEN_EXPIRED')`: the token's `exp` claim is in the past relative to the server clock. Re-wrapped as `AuthSessionHttpError` with HTTP 401. The auth session itself may still be valid; only the short-lived access token expired.","triggerScenarios":"Using an access token past its `accessTokenTtl` lifetime without refreshing, or a client clock that minted/kept a token whose expiry already passed.","commonSituations":"Long-running client sessions where the access token was never refreshed, background tabs waking after sleep with a stale token, or clock drift between client and server.","solutions":["Call `POST /api/auth/session/refresh` with the refresh token to obtain a new access token, then retry.","Implement proactive refresh before the access token's `expiresIn` elapses.","Handle 401 access_token_expired by transparently refreshing once and replaying the request."],"exampleFix":"// before\nfetch(url, { headers: { authorization: `Bearer ${expiredToken}` } }); // 401\n\n// after\nif (tokenExpired(accessToken)) {\n  const refreshed = await refreshSession(refreshToken);\n  accessToken = refreshed.accessToken;\n}\nfetch(url, { headers: { authorization: `Bearer ${accessToken}` } });","handlingStrategy":"retry","validationCode":"function tokenExpiresIn(token: string, now = Date.now()): number {\n  const p = JSON.parse(atob(token.split('.')[1]));\n  return (p.exp * 1000) - now;\n}\nif (tokenExpiresIn(accessToken) < 30_000) {\n  ({ accessToken } = await refreshSession(refreshToken));\n}","typeGuard":null,"tryCatchPattern":"async function fetchWithRefresh(url: string, init: RequestInit): Promise<Response> {\n  let res = await fetch(url, { ...init, headers: { ...init.headers, authorization: `Bearer ${accessToken}` } });\n  if (res.status === 401) {\n    const body = await res.clone().json().catch(() => ({}));\n    if (body.code === 'access_token_expired') {\n      ({ accessToken } = await refreshSession(refreshToken));\n      res = await fetch(url, { ...init, headers: { ...init.headers, authorization: `Bearer ${accessToken}` } });\n    }\n  }\n  return res;\n}","preventionTips":["Refresh the access token before its `expiresIn` elapses.","Handle 401 access_token_expired by refreshing once and replaying the request.","Track token expiry client-side to avoid sending obviously-expired tokens."],"tags":["authentication","jwt","access-token","expired","refresh"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}