actualbudget/actual · error
Authorization failed: missing state parameter.
Error message
Authorization failed: missing state parameter.
What it means
The Enable Banking OAuth callback requires a `state` query parameter to correlate the redirect with a pending authorization session. When `state` is absent the handler returns HTTP 400 with this HTML page. The state parameter is the CSRF/session correlation token for the pending auth, so the flow cannot proceed without it.
Source
Thrown at packages/sync-server/src/app-enablebanking/app-enablebanking.ts:116
// Auth callback from bank redirect — must be before validateSessionMiddleware
// since the bank redirects here directly (no auth token available)
app.get('/auth_callback', async (req: Request, res: Response) => {
const code = typeof req.query.code === 'string' ? req.query.code : undefined;
const state =
typeof req.query.state === 'string' ? req.query.state : undefined;
if (!code) {
res
.status(400)
.send(
'<html><body><p>Authorization failed: missing code.</p></body></html>',
);
return;
}
if (!state) {
res
.status(400)
.send(
'<html><body><p>Authorization failed: missing state parameter.</p></body></html>',
);
return;
}
try {
const session = await enableBankingService.createSession(code);
debug(
'Callback session created: %s with %d accounts',
session.session_id,
session.accounts.length,
);
const result = await buildSessionResult(session, extractPsuHeaders(req));
// Always cache the result so retries within TTL can read it
completedAuths.set(state, result);View on GitHub (pinned to d4334cb6e6)
Solutions
- Restart the authorization flow so a fresh state is generated and echoed back by the provider
- Ensure the outbound authorization request includes a state parameter
- Do not invoke the callback URL directly; always complete the provider-hosted authorization step
- Check for middleware/proxies that may strip query parameters from the redirect
Example fix
// before GET /enablebanking-handler?code=abc -> 400 missing state // after GET /enablebanking-handler?code=abc&state=<pending-session-state>
Defensive patterns
Strategy: validation
Validate before calling
const url = new URL(callbackUrl);
if (!url.searchParams.get('state')) {
throw new Error('callback URL missing state; restart the OAuth flow');
} Type guard
function hasState(q: Record<string, unknown>): q is { state: string } {
return typeof q.state === 'string' && q.state.length > 0;
} Try / catch
null
Prevention
- Always include state in the outbound authorization request
- Complete the flow in one browser session without manually editing URLs
- Check proxies/middleware don't strip query params
- Store state client-side so you can detect a dropped state before calling the server
When it happens
Trigger: A request hits the Enable Banking callback route with a `code` but no `state` query parameter — typically a hand-crafted or replayed callback URL, or a provider that drops the state on redirect.
Common situations: Testing the callback URL manually in a browser; third-party tools that strip query parameters; misconfigured authorization request that omitted state from the outbound URL while the callback still expects it.
Related errors
- Authorization failed: missing code.
- Authorization failed. You can close this window and try agai
- No token received.
- Parameter ${name} not provided to query
- NOT_CONFIGURED
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/ef323e7f1b67ca85.
Report an issue: GitHub.