{"record":{"id":"17e443e75b375890","repo":"flarum/framework","slug":"csrf-token-did-not-match","errorCode":null,"errorMessage":"CSRF token did not match","messagePattern":"CSRF token did not match","errorType":"http","errorClass":"TokenMismatchException","httpStatus":419,"severity":"error","filePath":"framework/core/src/Http/Middleware/CheckCsrfToken.php","lineNumber":43,"sourceCode":"    public function process(Request $request, Handler $handler): Response\n    {\n        if (in_array($request->getAttribute('routeName'), $this->exemptRoutes, true)) {\n            return $handler->handle($request);\n        }\n\n        if (in_array($request->getMethod(), ['GET', 'HEAD', 'OPTIONS'])) {\n            return $handler->handle($request);\n        }\n\n        if ($request->getAttribute('bypassCsrfToken', false)) {\n            return $handler->handle($request);\n        }\n\n        if ($this->tokensMatch($request)) {\n            return $handler->handle($request);\n        }\n\n        throw new TokenMismatchException('CSRF token did not match');\n    }\n\n    private function tokensMatch(Request $request): bool\n    {\n        $expected = (string) $request->getAttribute('session')->token();\n\n        $provided = $request->getParsedBody()['csrfToken'] ??\n            $request->getHeaderLine('X-CSRF-Token');\n\n        return hash_equals($expected, $provided);\n    }\n}\n","sourceCodeStart":25,"sourceCodeEnd":56,"githubUrl":"https://github.com/flarum/framework/blob/4b939f685389bfe8a380e9e28ddf305a1c66950c/framework/core/src/Http/Middleware/CheckCsrfToken.php#L25-L56","documentation":"CheckCsrfToken::process throws TokenMismatchException('CSRF token did not match') when a non-safe request's session CSRF token doesn't match the token submitted with the request. Flarum guards all state-changing requests against cross-site request forgery; only requests whose tokens match proceed down the middleware stack.","triggerScenarios":"POST/PUT/DELETE requests missing the X-CSRF-Token header or csrfParam form field; requests after session expiry or regeneration; requests sent cross-origin (another site posting to the forum API); API calls that bypass Flarum's csrfToken acquisition step.","commonSituations":"Long-lived browser tabs where the session rotated while the page still holds the old token; custom API scripts posting directly without first fetching the CSRF token from the forum page/session; multiple tabs invalidating each other's tokens.","solutions":["Include the current CSRF token in requests: header 'X-CSRF-Token' from the session (or the csrfToken exposed by Flarum's frontend payload).","Refresh the page / re-fetch a session and its token when it has expired, then retry the request once.","For server-to-server calls, authenticate via API keys (e.g. the api_token mechanism) instead of session cookies, which don't need CSRF tokens."],"exampleFix":"// before\nfetch('/api/discussions', { method: 'POST', body: data });\n\n// after\nfetch('/api/discussions', {\n  method: 'POST',\n  headers: { 'X-CSRF-Token': app.session.csrfToken },\n  body: data\n});","handlingStrategy":"try-catch","validationCode":"// ensure a token exists and is attached before sending\nif (!app.session.csrfToken) await refreshSession();\nconst headers = { 'X-CSRF-Token': app.session.csrfToken };","typeGuard":"const hasCsrf = (req) => typeof req.headers?.['X-CSRF-Token'] === 'string' && req.headers['X-CSRF-Token'].length > 0;","tryCatchPattern":"try {\n  return await apiFetch('/api/discussions', { method: 'POST', headers, body });\n} catch (e) {\n  if (isTokenMismatch(e)) { await refreshSession(); return retryWithFreshToken(); }\n  throw e;\n}","preventionTips":["Always send X-CSRF-Token (or the csrf form param) with state-changing requests.","Refresh the session/token after idle timeouts before retrying.","Use API tokens instead of session cookies for non-browser clients.","Avoid keeping stale tabs open across session rotation; reload before acting."],"tags":["flarum","csrf","security","http","middleware"],"backgroundTag":"csrf-token-mismatch","analyzedSha":"4b939f685389bfe8a380e9e28ddf305a1c66950c","analyzedAt":"2026-09-15T18:09:20.879Z","contentChangedAt":"2026-09-15T18:09:20.879Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}