toeverything/AFFiNE · error · OauthStateExpired
oauth_state_expired
oauth_state_expired
Error message
OAuth state expired, please try again.
What it means
OauthStateExpired thrown at packages/backend/server/src/plugins/oauth/service.ts:100 when the 36-char state UUID is well-formed but not found in the challenge store (AuthChallengeStore over SessionCache with a 3-hour TTL, see OAUTH_STATE_TTL_MS). The state was never issued, has already expired, was consumed, or lives on another server node's cache.
Solutions
- Have the user restart the sign-in flow — a fresh preflight issues a new state.
- For multi-instance deployments, point all nodes at the same shared Redis session cache so states are visible cluster-wide.
- If states expire too fast in your environment, ensure the cache backend is persistent (Redis AOF/RDB) rather than in-memory.
- Check for clock/TTL misconfiguration or Redis maxmemory eviction policy that is evicting oauth_state keys.
Defensive patterns
Strategy: retry
Try / catch
try {
await oauth.verifyCallback(input);
} catch (err) {
if (err instanceof OauthStateExpired) {
// user-facing: 'session expired, please sign in again' -> re-run preflight
}
} Prevention
- Share one Redis session cache across all backend replicas.
- Use a persistent Redis (AOF/RDB) so restarts do not wipe oauth_state keys.
- Complete login promptly; states live 3 hours.
- Avoid Redis eviction policies that evict auth_challenge:* keys under memory pressure.
When it happens
Trigger: Completing the OAuth callback more than 3 hours after preflight; server restart that wiped an in-memory/Redis cache without persistence; multi-instance deployment where the callback hits a node that does not share the session cache; the user's browser replaying an old callback; a state UUID that was fabricated (valid format, never saved).
Common situations: Self-hoster running several backend replicas behind a load balancer with cache pointing at different Redis DBs or memory cache; Redis flushed/evicted; user left the login tab open overnight; Docker restart with AFFINE default in-memory cache in production.
Related errors
AI-assisted analysis of toeverything/AFFiNE@d897bb3d84 (2026-08-18).
Data as JSON: /api/errors/9086412e5b5f9352.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/plugins/oauth/service.ts:107
redirectUri?: string;
client: string;
clientNonce: string;
clientVersion?: string;
callbackUrl: string;
redirectBaseUrl: string;
redirectAllowedOrigins: string[];
redirectTrustedDomains: string[];
}) {
return await this.call<{ url: string }>({
action: 'oauth_preflight',
...input,
});
}
async callback(input: {
code: string;
state: string;
clientNonce?: string;
issue: SessionIssueInput;
}) {
return await this.call<NativeOAuthCallback>({
action: 'oauth_callback',
...input,
});
}
private async call<T>(input: Record<string, unknown>) {
try {
return await this.runtime.executeAuthSessionCommandV1<T>(input);
} catch (error) {
const message = String(error);
if (message.includes('unknown_oauth_provider')) {
throw new UnknownOauthProvider({ name: String(input.provider ?? '') });
}
if (message.includes('invalid_oauth_callback_state')) {
throw new InvalidOauthCallbackState();View on GitHub (pinned to d897bb3d84)