toeverything/AFFiNE · error · AuthSessionHttpError
access_token_invalid
access_token_invalid
Error message
The access token is invalid.
What it means
Surfaced in `resolveRequestSession` when `signInWithJwt` → `AccessTokenService.verify` throws `SessionAccessTokenError('ACCESS_TOKEN_INVALID')`, re-wrapped as `AuthSessionHttpError`. Triggers include: the token's key id is unknown/retired, signature verification failed, the token lacks `authSessionId`/`userId`, the referenced auth session or user does not match, or the user was not found. HTTP 401.
Source
Thrown at packages/backend/server/src/core/auth/guard.ts:144
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 26c515e050)
Solutions
- Discard the stored access token and re-authenticate (or refresh via the refresh token).
- If keys were rotated, ensure clients refresh tokens after rotation completes.
- Verify the token source is not truncating or re-encoding the JWT.
Example fix
// before
fetch(url, { headers: { authorization: `Bearer ${staleToken}` } }); // 401 access_token_invalid
// after
const { accessToken } = await refreshSession(refreshToken);
fetch(url, { headers: { authorization: `Bearer ${accessToken}` } }); Defensive patterns
Strategy: try-catch
Validate before calling
function decodeJwtExp(token: string): number | null {
try {
const p = JSON.parse(atob(token.split('.')[1]));
return typeof p.exp === 'number' ? p.exp : null;
} catch { return null; }
}
// detect structurally invalid tokens before sending
if (!/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) {
await discardAndReauth();
} Type guard
function looksLikeJwt(v: string): boolean {
return /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(v);
} Try / catch
if (res.status === 401 && body.code === 'access_token_invalid') {
// token is structurally/signature invalid or key removed — cannot refresh, re-auth
await signOut();
redirect('/sign-in');
} Prevention
- Do not reuse access tokens across key rotations — refresh after rotation.
- Store tokens verbatim; avoid truncation, re-encoding, or URL-decoding twice.
- On 401 access_token_invalid, discard the token and re-authenticate (refresh will also fail).
When it happens
Trigger: Presenting a JWT whose signing key was deleted, a tampered token, a token minted for a different user/session, or one referencing an auth session that no longer exists.
Common situations: Signing keys were rotated/removed and the client still holds an old token, a token was corrupted in transit or storage, or the user/account was deleted.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- access_token_expired
- authentication_required
- auth_session_temporarily_unavailable
- action_forbidden
- wrong_sign_in_credentials
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/7b17bae12a687db5.
Report an issue: GitHub.