toeverything/AFFiNE · error · AuthSessionHttpError

The refresh token is invalid. | The auth session has expired

Error message

The refresh token is invalid. | The auth session has expired. | The auth session has been revoked. | The refresh token has already been used. | Auth session service is temporarily unavailable.

What it means

Thrown by SessionExchangeService.refresh when authSessions.refresh returns a status other than 'rotated'. It is an AuthSessionHttpError whose `code` (lowercased) and HTTP status derive from refreshed.code: AUTH_SESSION_TEMPORARILY_UNAVAILABLE -> 503 network_error; any of REFRESH_TOKEN_INVALID / AUTH_SESSION_EXPIRED / AUTH_SESSION_REVOKED / REFRESH_TOKEN_REUSED -> 401 authentication_required. The aggregated message lists all five possibilities because the catalog covers the union; the concrete instance carries exactly one code.

Source

Thrown at packages/backend/server/src/core/auth/session-exchange.ts:124

      issued.session.absoluteExpiresAt
    );
  }

  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,

View on GitHub (pinned to 26c515e050)

Solutions

  1. On 401 codes (invalid/expired/revoked/reused), clear local credentials and route the user to sign-in.
  2. On 503 (temporarily_unavailable), retry with exponential backoff after confirming the cache/store is healthy.
  3. Inspect the concrete `code` on the error to choose between 'sign in again' vs. 'try again later'.

Example fix

// before: treat every failure as a network blip and retry
const t = await refresh();

// after: branch on the concrete auth code
try {
  const t = await refresh();
} catch (e) {
  if (e.code === 'auth_session_temporarily_unavailable') { await backoffRetry(); }
  else { await signOutAndShowLogin(); } // invalid|expired|revoked|reused
}
Defensive patterns

Strategy: try-catch

Type guard

function isTransient(code: string): boolean {
  return code === 'auth_session_temporarily_unavailable';
}
function isRecoverableBySignIn(code: string): boolean {
  return ['refresh_token_invalid', 'auth_session_expired', 'auth_session_revoked', 'refresh_token_reused'].includes(code);
}

Try / catch

try {
  await refresh(req, refreshToken, appVersion);
} catch (e) {
  if (!(e instanceof AuthSessionHttpError)) throw e;
  if (e.authCode === AuthSessionErrorCode.temporarilyUnavailable) {
    await backoffRetry(); // 503 path
  } else {
    await clearCredentialsAndSignIn(); // 401 path: invalid|expired|revoked|reused
  }
}

Prevention

When it happens

Trigger: Calling refresh (session-exchange.ts:118-124) with a refresh token that authSessions.refresh rejects: token structurally invalid or not found (invalid), session TTL elapsed (expired), session revoked by user/admin (revoked), token already used once and not rotated (reused), or the underlying store (cache/db) is unreachable (temporarily_unavailable).

Common situations: App resumed after a long sleep and the session expired; user clicked 'Sign out everywhere' on another device (revoked); a token-reuse attack triggered automatic revocation; a Redis outage makes the session store temporarily unavailable; a stale token persisted across an app reinstall.

Understand the failure class

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/e9c640a960e953f2. Report an issue: GitHub.