toeverything/AFFiNE · error · AuthSessionHttpError
auth_session_revoked
auth_session_revoked
Error message
The auth session has been revoked.
What it means
Thrown by SessionExchangeService.refresh when authSessions.refresh returned 'rotated' (success) but the subsequent authSessions.get(refreshed.authSessionId) returns null. It is an AuthSessionHttpError with code AUTH_SESSION_REVOKED and default HTTP 401. This handles the narrow race where the session is revoked between the rotation step and the read-back.
Source
Thrown at packages/backend/server/src/core/auth/session-exchange.ts:128
async refresh(req: Request, refreshToken: string, appVersion?: string) {
if (!isNativeClientRequest(req)) throw new ActionForbidden();
const selector = refreshToken.split('.')[1];
if (selector) {
const rateKey = `auth:session-refresh-rate:${selector}`;
const attempts = await this.cache.increaseWithTtl(rateKey, 60_000);
if (attempts > 30) throw new TooManyRequest();
}
const refreshed = await this.authSessions.refresh(refreshToken, appVersion);
if (refreshed.status !== 'rotated') {
const status =
refreshed.code === AuthSessionErrorCode.temporarilyUnavailable
? HttpStatus.SERVICE_UNAVAILABLE
: HttpStatus.UNAUTHORIZED;
throw new AuthSessionHttpError(refreshed.code, status);
}
const session = await this.authSessions.get(refreshed.authSessionId);
if (!session) {
throw new AuthSessionHttpError(AuthSessionErrorCode.revoked);
}
return this.tokenPair(
session.userSession.userId,
refreshed.authSessionId,
refreshed.refreshToken,
refreshed.refreshExpiresAt,
session.absoluteExpiresAt
);
}
private async tokenPair(
userId: string,
authSessionId: string,
refreshToken: string,
refreshTokenExpiresAt: Date,
absoluteExpiresAt: Date
) {
const access = await this.accessTokens.sign(userId, authSessionId);View on GitHub (pinned to 26c515e050)
Solutions
- Treat this exactly like a revoked session: discard the refresh token and require a fresh sign-in.
- Investigate concurrent refreshers if it happens repeatedly (multiple devices/tabs sharing one token).
- If caused by store replication lag, verify the cache/DB read-after-write consistency for auth sessions.
Example fix
// before: assume rotation success means a usable session
const t = await refresh(); // may throw auth_session_revoked post-rotate
// after: handle the post-rotate revoked race
try {
const t = await refresh();
} catch (e) {
if (e.code === 'auth_session_revoked') { await forceSignIn(); }
else { throw e; }
} Defensive patterns
Strategy: try-catch
Type guard
function isPostRotateRevoked(e: unknown): boolean {
return e instanceof AuthSessionHttpError && e.authCode === AuthSessionErrorCode.revoked;
} Try / catch
try {
return await refresh(req, refreshToken, appVersion);
} catch (e) {
if (e instanceof AuthSessionHttpError && e.authCode === AuthSessionErrorCode.revoked) {
await forceSignIn(); // session vanished between rotate and read
} else throw e;
} Prevention
- Treat a post-rotate revoked race the same as any revocation: re-authenticate.
- Avoid concurrent refreshers sharing one refresh token (multiple devices/tabs).
- Verify read-after-write consistency of the auth-session store if this recurs.
When it happens
Trigger: Calling refresh (session-exchange.ts:126-129): rotation succeeds, but between rotation and get() the session is deleted/revoked (concurrent 'sign out everywhere', admin action, or reuse-detection on another device).
Common situations: Two devices refreshing the same session near-simultaneously where one triggers revocation; an admin or security policy revoking sessions in the moment between rotate and read; a DB/cache consistency lag where the just-written session is not yet readable.
Related errors
- too_many_request
- The refresh token is invalid. | The auth session has expired
- action_forbidden
- action_forbidden
- invalid_auth_state
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/45625033fc40858e.
Report an issue: GitHub.