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

  1. On 401 with these codes, call POST /api/auth/session/refresh with the refresh token, then retry the original request once
  2. If refresh returns auth_session_revoked / refresh_token_reused / refresh_token_invalid, drop all tokens and restart sign-in
  3. Track token expiry (expiresAt returned at issue time) and refresh proactively instead of waiting for 401
  4. 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

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


AI-assisted analysis of toeverything/AFFiNE@591f874dad (2026-08-18). Data as JSON: /api/errors/cbca18c69b1d2eed. Report an issue: GitHub.