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

  1. Restart the authorization flow so a fresh state is generated and echoed back by the provider
  2. Ensure the outbound authorization request includes a state parameter
  3. Do not invoke the callback URL directly; always complete the provider-hosted authorization step
  4. 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

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


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/ef323e7f1b67ca85. Report an issue: GitHub.