chatboxai/chatbox · error · Error
OAuth state mismatch
Error message
OAuth state mismatch
What it means
Thrown by OpenAI's callback OAuth provider after the local callback server (port 1455) receives a redirect whose state parameter does not equal the random state generated at login start. This is the standard OAuth CSRF defense: a mismatch means the callback did not correspond to this login attempt.
Source
Thrown at src/main/oauth/providers/openai.ts:78
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('client_id', CLIENT_ID)
authUrl.searchParams.set('redirect_uri', REDIRECT_URI)
authUrl.searchParams.set('scope', SCOPE)
authUrl.searchParams.set('code_challenge', challenge)
authUrl.searchParams.set('code_challenge_method', 'S256')
authUrl.searchParams.set('state', state)
authUrl.searchParams.set('id_token_add_organizations', 'true')
authUrl.searchParams.set('codex_cli_simplified_flow', 'true')
authUrl.searchParams.set('originator', 'chatbox')
const { promise, close } = createCallbackServer(CALLBACK_PORT, signal, CALLBACK_HOST)
try {
await openUrl(authUrl.toString())
const result = await promise
if (result.state !== state) {
throw new Error('OAuth state mismatch')
}
return await exchangeCodeForTokens(result.code, verifier)
} finally {
close()
}
},
async refreshToken(credentials) {
if (!credentials.refreshToken) {
throw new Error('No refresh token available')
}
const response = await fetch(TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',View on GitHub (pinned to 81571269ad)
Solutions
- Treat as a hard security failure: do not exchange the code; restart login() from scratch.
- Ensure only one login() is in flight at a time per provider (serialize or cancel prior flows).
- Close stale browser tabs on the authorize page before starting a new login.
- Confirm createCallbackServer is bound to localhost only so external callers cannot inject callbacks.
Example fix
// before
if (result.state !== state) {
throw new Error('OAuth state mismatch')
}
// after
if (result.state !== state) {
log.error('[OAuth:OpenAI] state mismatch — possible stale or forged callback')
throw new SecurityError('Login state mismatch. Close other ChatGPT login tabs and try again.')
} Defensive patterns
Strategy: try-catch
Try / catch
try {
return await provider.login({ openUrl, signal })
} catch (e) {
if (/state mismatch/i.test(String(e))) {
// security: never exchange a mismatched callback; restart fresh
throw new SecurityError('Login state mismatch. Close other ChatGPT login tabs and retry.')
}
throw e
} Prevention
- Serialize login() per provider — only one in flight at a time.
- Bind the callback server to localhost only (CALLBACK_HOST='localhost') so external callers cannot inject callbacks.
- Close stale browser tabs on the OpenAI authorize page before starting a new login.
- Treat any state mismatch as a potential CSRF attack; never bypass the check.
When it happens
Trigger: Browser was already on a stale authorize page from a previous login and redirected with an old state; a second login tab raced the first and the wrong callback arrived; user manually visited localhost:1455 with a crafted/old URL; an attacker injected a callback (the very attack the guard prevents).
Common situations: User had multiple ChatGPT login tabs open; a previous login's browser tab redirected after a new login started; redirects cached by the browser; testing tools replaying an old callback URL.
Related errors
- MiniMax OAuth state mismatch
- No refresh token available
- Token refresh failed: ${response.status}
- Token refresh response missing access_token
- Token exchange failed: ${response.status}
AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12).
Data as JSON: /api/errors/450ea1cebdbcd0b0.
Report an issue: GitHub.