{"record":{"id":"12a10b85ab6b921f","repo":"bytedance/deer-flow","slug":"csrf-token-missing-include-x-csrf-token-header","errorCode":null,"errorMessage":"CSRF token missing. Include X-CSRF-Token header.","messagePattern":"CSRF token missing\\. Include X-CSRF-Token header\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"backend/app/gateway/langgraph_auth.py","lineNumber":49,"sourceCode":"\ndef _check_csrf(request) -> None:\n    \"\"\"Enforce Double Submit Cookie CSRF check for state-changing requests.\n\n    Mirrors Gateway's CSRFMiddleware logic so that LangGraph routes\n    proxied directly by nginx have the same CSRF protection.\n    \"\"\"\n    method = getattr(request, \"method\", \"\") or \"\"\n    if method.upper() not in _CSRF_METHODS:\n        return\n\n    if is_auth_disabled():\n        return\n\n    cookie_token = request.cookies.get(\"csrf_token\")\n    header_token = request.headers.get(\"x-csrf-token\")\n\n    if not cookie_token or not header_token:\n        raise Auth.exceptions.HTTPException(\n            status_code=403,\n            detail=\"CSRF token missing. Include X-CSRF-Token header.\",\n        )\n\n    if not secrets.compare_digest(cookie_token, header_token):\n        raise Auth.exceptions.HTTPException(\n            status_code=403,\n            detail=\"CSRF token mismatch.\",\n        )\n\n\n@auth.authenticate\nasync def authenticate(request):\n    \"\"\"Validate the session cookie, decode JWT, and check token_version.\n\n    Same validation chain as Gateway's get_current_user_from_request:\n      cookie → decode JWT → DB lookup → token_version match\n    Also enforces CSRF on state-changing methods.","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/langgraph_auth.py#L31-L67","documentation":"HTTP 403 from the LangGraph-compatible runtime's CSRF guard: for state-changing methods (POST, PUT, DELETE, PATCH), when auth is enabled, the request must carry BOTH a csrf_token cookie and an X-CSRF-Token header. Missing either raises 'CSRF token missing. Include X-CSRF-Token header.' This is double-submit-cookie CSRF protection on the proxied /api/langgraph/* surface.","triggerScenarios":"POST/PUT/DELETE/PATCH to any LangGraph-runtime route (e.g. runs/threads mutations through nginx /api/langgraph/) without the X-CSRF-Token header, without the csrf_token cookie, or both — typically a non-browser client or a fetch that forwards cookies but not custom headers.","commonSituations":"Script/curl client that authenticates with cookies but never reads the Set-Cookie csrf_token to echo it in a header; frontend fetch omitting the X-CSRF-Token header after a refactor; CORS preflight stripping the custom header; auth disabled flag turned off (enabled auth) in an environment where the client was built assuming no CSRF.","solutions":["Read csrf_token from the login response cookies and send it back on every mutating call: header 'X-CSRF-Token: <value>' with credentials included","In browser code use a shared fetch wrapper that attaches the header from document.cookie for POST/PUT/DELETE/PATCH","For curl: curl -b jar -c jar -H \"X-CSRF-Token: $(awk '/csrf_token/{print $7}' jar)\" ...","Ensure CORS config (if cross-origin) allows the X-CSRF-Token request header"],"exampleFix":"// before: 403 CSRF token missing\nawait fetch('/api/langgraph/threads', {\n  method: 'POST', credentials: 'include',\n  headers: { 'content-type': 'application/json' }, body: '{}',\n});\n// after\nconst csrf = document.cookie.match(/csrf_token=([^;]+)/)?.[1];\nawait fetch('/api/langgraph/threads', {\n  method: 'POST', credentials: 'include',\n  headers: { 'content-type': 'application/json', 'X-CSRF-Token': csrf }, body: '{}',\n});","handlingStrategy":"validation","validationCode":"function csrfToken(): string | null {\n  const m = document.cookie.match(/(?:^|; )csrf_token=([^;]+)/);\n  return m ? decodeURIComponent(m[1]) : null;\n}\n// guard before any mutation\nconst t = csrfToken();\nif (!t) throw new Error('no csrf cookie — login first');\nfetch(url, { method: 'POST', credentials: 'include', headers: { 'X-CSRF-Token': t } });","typeGuard":"null","tryCatchPattern":"catch (e) {\n  if (e.status === 403 && e.detail?.includes('CSRF token missing')) {\n    await refreshSession();  // obtain csrf cookie, then replay once\n    return fetchOnce(url, opts);\n  }\n  throw e;\n}","preventionTips":["Route every mutating call through one client wrapper that injects X-CSRF-Token from the cookie","Login response handler must persist the csrf_token cookie before any POST","Only POST/PUT/DELETE/PATCH need the header — leave GETs clean","If auth is disabled in your env, the guard is skipped — don't build clients that rely on that"],"tags":["csrf","http-403","security","langgraph","cookies","headers"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}