toeverything/AFFiNE · error · AuthSessionHttpError
${err.code}
Error message
${err.code} What it means
When a request carries an Authorization: Bearer value that looks like a JWT, AuthGuard verifies it as an auth-session access token. Failures raise SessionAccessTokenError with a code of ACCESS_TOKEN_EXPIRED, ACCESS_TOKEN_INVALID, AUTH_SESSION_EXPIRED, or AUTH_SESSION_REVOKED (see packages/backend/server/src/core/auth/access-token.ts:14), which the guard re-throws as AuthSessionHttpError - HTTP 401 whose message is the raw code and whose JSON code is the lowercase form (e.g. access_token_expired).
Source
Thrown at packages/backend/server/src/core/auth/guard.ts:104
const result = await this.resolveRequestSession(req, res, isPublic);
return result?.session ?? null;
}
private async resolveRequestSession(
req: Request,
res?: Response,
isPublic = false
): Promise<AuthenticatedRequestSession | null> {
const bearer = req.headers.authorization
? extractTokenFromHeader(req.headers.authorization)
: undefined;
if (bearer && isLikelyJwt(bearer)) {
try {
const session = await this.signInWithJwt(req, bearer, res, isPublic);
return session ? { type: 'jwt', session } : null;
} catch (err) {
if (err instanceof SessionAccessTokenError) {
throw new AuthSessionHttpError(err.code);
}
throw err;
}
}
const session = await this.signInWithCookie(req, res, isPublic);
return session ? { type: 'cookie_session', session } : null;
}
async signInWithJwt(
req: Request,
token: string,
res?: Response,
isPublic = false
): Promise<Session | null> {
if (req.session && req.authType === 'jwt') return req.session;
const session = await this.accessTokens.verify(token);
const versionAllowed = await this.checkUserSessionClientVersion(View on GitHub (pinned to 591f874dad)
Solutions
- On 401 with these codes, call POST /api/auth/session/refresh with the refresh token, then retry the original request once
- If refresh returns auth_session_revoked / refresh_token_reused / refresh_token_invalid, drop all tokens and restart sign-in
- Track token expiry (expiresAt returned at issue time) and refresh proactively instead of waiting for 401
- After server key rotation, expect ACCESS_TOKEN_INVALID until clients refresh
Example fix
// before
const res = await fetch(url, { headers: { authorization: `Bearer ${accessToken}` } });
// after
let res = await fetch(url, { headers: { authorization: `Bearer ${accessToken}` } });
if (res.status === 401) {
const code = (await res.json()).code; // access_token_expired | access_token_invalid | auth_session_expired | auth_session_revoked
if (code === 'auth_session_revoked') return forceRelogin();
({ accessToken } = await refreshSession(refreshToken));
res = await fetch(url, { headers: { authorization: `Bearer ${accessToken}` } });
} Defensive patterns
Strategy: retry
Validate before calling
function tokenNeedsRefresh(expiresAt: number): boolean {
return Date.now() >= expiresAt - 30_000; // refresh 30s before expiry
} Type guard
const AUTH_SESSION_CODES = new Set([
'access_token_expired',
'access_token_invalid',
'auth_session_expired',
'auth_session_revoked',
]);
function isAuthSessionHttpError(e: unknown): e is { code: string } {
return typeof e === 'object' && e !== null && AUTH_SESSION_CODES.has((e as { code?: string }).code ?? '');
} Try / catch
try {
return await api.get(url);
} catch (e) {
if (!isAuthSessionHttpError(e)) throw e;
if ((e as { code: string }).code === 'auth_session_revoked') return forceRelogin();
({ accessToken } = await refreshSession(refreshToken)); // POST /auth/session/refresh
return await api.get(url); // single retry with the new token
} Prevention
- Wrap all bearer-authenticated calls in a refresh-and-retry-once interceptor
- Track expiresAt and refresh proactively before requests fail
- Treat revoked/reused refresh tokens as terminal - clear storage and re-login
When it happens
Trigger: Access token past its accessTokenTtl; token signed by a rotated-out or deleted signing key; the underlying auth session expired (auth_session_expired) or was revoked via /session/revoke or 'sign out everywhere' while the client kept using the old access token.
Common situations: Native/Electron clients that cache tokens without refreshing; signing-key ring rotation invalidating outstanding tokens; user revoking the session from another device; long-lived automated scripts holding one token forever.
Related errors
- authentication_required
- action_forbidden
- AUTH_SESSION_REVOKED
- action_forbidden
- wrong_sign_in_credentials
AI-assisted analysis of toeverything/AFFiNE@591f874dad (2026-08-18).
Data as JSON: /api/errors/cbca18c69b1d2eed.
Report an issue: GitHub.