toeverything/AFFiNE · error · InvalidAuthState
invalid_auth_state
invalid_auth_state
Error message
Invalid auth state. You might start the auth progress from another device.
What it means
OIDCProvider.getToken exchanges the authorization code at the IdP token endpoint. If this provider requires PKCE but the stored OAuthState lacks state.pkce.codeVerifier, it throws InvalidAuthState before making the request — the verifier is needed to prove the code belongs to this flow, and without it the exchange cannot proceed. The verifier is created together with the challenge during preflight; a state record with a challenge but no verifier is inconsistent.
Source
Thrown at packages/backend/server/src/plugins/oauth/providers/oidc.ts:285
'claim_email_verified'
),
state,
nonce,
};
if (pkce) {
query.code_challenge = pkce.codeChallenge;
query.code_challenge_method = pkce.codeChallengeMethod;
}
return `${this.endpoints.authorization_endpoint}?${this.url.stringify(
query
)}`;
}
async getToken(code: string, state: OAuthState): Promise<Tokens> {
if (this.requiresPkce && !state.pkce?.codeVerifier) {
throw new InvalidAuthState();
}
const data = await this.postFormJson<unknown>(
this.endpoints.token_endpoint,
this.url.stringify({
code,
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
redirect_uri: this.url.link('/oauth/callback'),
grant_type: 'authorization_code',
...(state.pkce?.codeVerifier
? { code_verifier: state.pkce.codeVerifier }
: {}),
}),
{ treatServerErrorAsInvalid: true }
);
const tokens = OIDCTokenSchema.parse(data);View on GitHub (pinned to b4c8548c09)
Solutions
- Restart the login flow end-to-end in one browser session: fresh preflight → authorize → callback
- Complete the callback on the same device/browser that initiated the preflight — the verifier lives in that flow's server-side state
- After server upgrades, expect in-flight OAuth sessions to fail; have users sign in again rather than resuming
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the callback completes against the same preflight that started the flow
const pending = sessionStorage.getItem('affine_oauth_state');
if (pending !== stateStr) {
throw new Error('Callback state does not match the pending login — restart the flow on one device');
} Type guard
function isInvalidAuthState(e: unknown): boolean {
return typeof e === 'object' && e !== null && (e as any).code === 'invalid_auth_state';
} Try / catch
try {
await post('/oauth/callback', { code, state });
} catch (e) {
if (isInvalidAuthState(e)) {
clearPendingOAuthSession();
return restartFromPreflight(); // verifier lives in the original flow's state — cannot be recovered
}
throw e;
} Prevention
- Complete the whole OAuth flow on the device and browser that started it
- Store which preflight issued the pending state and abort early on mismatch instead of exchanging the code
- During server upgrades, drain in-flight OAuth sessions or expect InvalidAuthState callbacks from stale states
When it happens
Trigger: Callback arrives with a state whose server-side record was written without a code verifier: state saved by an older server version, a state envelope from a different flow type (challenge present, verifier dropped), or session/state mismatch where the callback's state maps to a record from another device or an incomplete preflight.
Common situations: Server upgrade while logins were in flight; user completing the callback in a different browser/device than the one that started preflight; multiple pending logins with mixed versions; 'start the auth progress from another device' is literally this case.
Related errors
- invalid_oauth_response
- wrong_sign_in_method
- missing_oauth_query_parameter
- unknown_oauth_provider
- oauth_state_expired
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/43651caa264256e8.
Report an issue: GitHub.