bytedance/deer-flow · warning · HTTPException

Missing code or state parameter

Error message

Missing code or state parameter

What it means

400 Bad Request from the SSO callback handler (auth.py:767) when the IdP's redirect back to /api/auth/sso/{provider}/callback lacks either the 'code' or the 'state' query parameter. In a healthy authorization-code flow the IdP always returns both; their absence means the callback URL was hand-built, mangled by a proxy, or the IdP is misbehaving (an IdP-side failure normally comes back as 'error'/'error_description' params, which are handled earlier with a 302 to 'sso_failed').

Source

Thrown at backend/app/gateway/routers/auth.py:767

    # ── Provider error ───────────────────────────────────────────────
    if error:
        logger.warning("OIDC provider returned error for %s: %s (description: %s)", provider, error, error_description)
        redirect = _build_error_redirect(oidc_config.frontend_base_url, "sso_failed")
        return RedirectResponse(url=redirect, status_code=status.HTTP_302_FOUND)

    if not oidc_config.enabled:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="SSO authentication is not enabled")

    if not _OIDC_PROVIDER_KEY_RE.match(provider):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid provider ID")

    provider_config = oidc_config.providers.get(provider)
    if not provider_config:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Unknown SSO provider: {provider}")

    if not code or not state:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing code or state parameter")

    # ── Verify state cookie ──────────────────────────────────────────
    state_payload = get_state_cookie(request, provider)
    if not state_payload:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Missing or expired OIDC state cookie")

    if not secrets.compare_digest(state_payload.state, state):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="OIDC state mismatch")

    # ── Resolve redirect URI ─────────────────────────────────────────
    redirect_uri = _resolve_oidc_redirect_uri(request, provider, provider_config)

    # ── Get metadata ─────────────────────────────────────────────────
    overrides = {
        "authorization_endpoint": provider_config.authorization_endpoint,
        "token_endpoint": provider_config.token_endpoint,
        "userinfo_endpoint": provider_config.userinfo_endpoint,
        "jwks_uri": provider_config.jwks_uri,

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Re-run the login flow from the start route and capture the exact redirect Location the IdP emits — confirm it carries both code and state
  2. Fix any proxy/rewrite rule that drops query strings on the callback path (avoid proxy_pass with a path component that discards the original URI query)
  3. Verify the IdP app's redirect URI matches the Gateway callback route exactly so the IdP appends parameters correctly

Example fix

# nginx (before) — path-style proxy_pass drops query handling subtleties
location /api/auth/sso/ {
    proxy_pass http://gateway:8001/api/auth/sso;  # trailing-slash mismatch
}

# after — clean pass-through preserving the full URI + query
location /api/auth/sso/ {
    proxy_pass http://gateway:8001;
}
Defensive patterns

Strategy: validation

Validate before calling

# Validate the IdP redirect Location before following it
loc = idp_response.headers["location"]
from urllib.parse import urlparse, parse_qs
q = parse_qs(urlparse(loc).query)
assert "code" in q and "state" in q, f"callback missing params: {loc}"

Try / catch

try { await callback() } catch (e) { if (e.status === 400 && /Missing code or state/.test(e.detail)) restartSsoFlow(); else throw e; }

Prevention

When it happens

Trigger: Direct navigation/bookmark of the callback URL with no query string; a reverse proxy or frontend router stripping query parameters during the redirect; a misconfigured IdP redirect URI template that drops ?code=...&state=...

Common situations: Nginx/proxy rewrite losing the query string (proxy_pass without URI vs with URI pitfalls); testing the callback by pasting the bare URL into a browser; WAF scrubbing query params.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/92b157d286ce895f. Report an issue: GitHub.