Fix 'CSRF Token Mismatch' Form Errors

By Bug Circuit Security Team
Fix 'CSRF Token Mismatch' Form Errors

A "CSRF token mismatch" error means the secret code your form sent does not match the one your server expected, and the usual causes are an expired session, a cached page serving an old token, or a cookie the browser dropped.

This guide is for site owners, developers and agencies whose contact forms, checkouts or logins are failing with this error. You will get a step-by-step checklist, a quick-reference table of error messages, and platform-specific fixes for Laravel, Django, WordPress and Rails. You will also learn which "fixes" to avoid, because some of them quietly make your site less safe.

What a CSRF token is and why it fails

CSRF stands for cross-site request forgery. It is an attack where a bad website tricks a logged-in visitor's browser into sending a request to your site, such as changing an email address or submitting a form.

The standard defense is a CSRF token. Your server puts a random, hard-to-guess value into every form and ties it to the visitor, usually through their session (the server's memory of who they are) or a cookie. When the form comes back, the server checks the two values match. OWASP's CSRF Prevention Cheat Sheet describes this pattern in detail.

So the error is the protection doing its job. Your goal is to find out why a real visitor's token failed, not to switch the check off.

What the error looks like on each platform

PlatformMessage you seeMost likely cause
Laravel"419 Page Expired" or "CSRF token mismatch"Session expired, cookie not sent, or missing @csrf
Django"CSRF verification failed. Request aborted."Missing {% csrf_token %}, cached page, or untrusted origin
RailsActionController::InvalidAuthenticityTokenCached form, token missing from a raw HTML form or JavaScript request, or session reset
WordPress"The link you followed has expired" or "Are you sure you want to do this?"Expired nonce (WordPress's version of a CSRF token), often from page caching
Custom or other"Invalid CSRF token"Any of the above

Step 1: Reproduce it and inspect the request

Before changing anything, see what the browser actually sends.

  1. Open the failing page in a private window.
  2. Open DevTools (F12) and go to the Network tab. Tick "Preserve log".
  3. Submit the form and click the failed request (usually a POST).
  4. Under Payload or Form Data, look for a field named like _token, csrfmiddlewaretoken or authenticity_token. For AJAX calls, check the request headers for X-CSRF-TOKEN or X-CSRFToken.
  5. Under Cookies (or the Application tab), check that your session cookie exists.

This tells you which problem you have:

  • No token in the request: the form or script is not including it (Step 5).
  • Token present, cookie missing: a cookie or domain problem (Step 4).
  • Both present but it still fails: the token is stale, so look at caching and sessions (Steps 2 and 3).

Step 2: Rule out session timeouts

In frameworks that store the token in the session (Laravel and Rails, for example), a token only works while that session is alive. If a visitor opens your form, goes to lunch, and submits an hour later, the session may be gone. The token no longer matches anything.

Django works differently. By default it keeps its CSRF secret in its own cookie, which lasts a year, so an expired login session does not by itself cause a Django mismatch.

Session timeouts are a frequent cause of "it works for me but not my customers" reports. Check these:

  • Session lifetime. In Laravel, SESSION_LIFETIME in .env is in minutes (the default is 120). In Django, look at SESSION_COOKIE_AGE (seconds). In Rails, check your session_store expiry.
  • Long forms. Quote requests, applications and checkouts take time. Raise the lifetime for those pages, or save progress as the visitor goes.
  • Multiple tabs. In some frameworks, logging in or out in one tab rotates the token and breaks forms open in another tab. Django does this on login, for example.
  • Server restarts. If sessions live in temporary files or memory, a deploy or restart can wipe them.

A friendlier fix than a longer timeout is to catch the error and send the visitor back to the form with their input kept and a short message, instead of a dead-end error page.

Step 3: Check for caching conflicts

Caching is an easy-to-miss cause. If a cache stores the HTML of a page that contains a form, every visitor gets the same token. That token belongs to someone else's session, or to a session that has expired, so the check fails.

Check what your server tells caches to do:

curl -s -D - -o /dev/null https://example.com/contact | grep -i -E "cache-control|age:|cf-cache-status|x-cache|set-cookie"

This sends a normal GET request, which is what real visitors send. Some caches treat HEAD requests (what curl -I sends) differently, so they can give misleading results.

Red flags:

  • cf-cache-status: HIT or x-cache: HIT on a page with a form.
  • An age: header with a large number.
  • A public Cache-Control such as public, max-age=3600 on a page that holds a token.

Fixes, from best to acceptable:

  1. Exclude form pages from full-page caching. Do this in your CDN (content delivery network, the service that serves copies of your pages from nearby servers), caching plugin, or server config.
  2. Send Cache-Control: private, no-store for pages with forms or logged-in content.
  3. Load the token with a small script after the page loads, from an endpoint that is never cached. This lets you cache the rest of the page.

In Django, wrapping a view in cache_page caches the token too. The Django CSRF docs cover how to handle this. Remember to purge your cache after you change the rules, or visitors keep getting the old copies.

Step 4: Fix cookie and domain problems

The token check usually depends on a cookie. If the browser does not send it back, the match fails.

  • www vs non-www. A visitor loads www.example.com but the form posts to example.com. Cookies set on one are not always sent to the other. Pick one version and redirect the other to it.
  • HTTP vs HTTPS. If your session cookie is marked Secure, the browser will not send it over plain HTTP. Redirect all HTTP to HTTPS.
  • Cookie domain setting. In Laravel, check SESSION_DOMAIN (for example .example.com to cover subdomains) and set SESSION_SECURE_COOKIE=true on HTTPS sites.
  • SameSite. The SameSite cookie attribute controls when cookies are sent in cross-site situations. A form embedded in an iframe on another site needs SameSite=None; Secure to send its cookie. See the MDN reference on SameSite. Do not loosen this for ordinary forms on your own domain.
  • Cookie blocking. Strict privacy settings or in-app browsers can drop cookies. Test in a second browser and on a phone.

Our security headers tool checks your site's security-related response headers. To see the exact Set-Cookie flags (Secure, SameSite), use the browser DevTools Application tab (Cookies) as in Step 1.

Step 5: Find the missing token

If Step 1 showed no token in the request, the form or script is not adding one.

  • Plain forms. Add @csrf (Laravel) or {% csrf_token %} (Django) inside the <form>. Rails: use form_with or form_for, which add the authenticity token automatically. Raw HTML forms do not get a token automatically, so add one yourself, for example <%= hidden_field_tag :authenticity_token, form_authenticity_token %> in Rails.
  • JavaScript requests. Read the token from a meta tag or cookie and send it as a header. In Laravel, put <meta name="csrf-token" content="{{ csrf_token() }}"> in your layout and send it as X-CSRF-TOKEN. In Rails, put <%= csrf_meta_tags %> in your layout <head> so scripts can read the token. In Django, send the X-CSRFToken header.
  • Third-party form embeds. If a widget posts to your server from another domain, it cannot read your token. Use the vendor's own endpoint, or build a server-side handoff.
  • Page builders and form plugins. Some copy form HTML into templates by hand. Re-create the form using the plugin's own block or shortcode.

Step 6: Check proxies, load balancers and multiple servers

If you run more than one server, a visitor's token may be created on server A and checked on server B. If sessions are stored in local files, B knows nothing about it.

  • Store sessions in a shared place such as Redis or your database.
  • Make sure your app knows it sits behind a proxy so it sees HTTPS correctly. In Django, set SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") only if your proxy sets that header reliably. In Laravel, configure trusted proxies.
  • In Django 4.0 and later, CSRF_TRUSTED_ORIGINS must include the scheme, for example ["https://www.example.com"]. Entries without a scheme fail Django's startup system check (4_0.E001) instead of causing a runtime error. If you see "Origin checking failed" with "does not match any trusted origins", the real origin is missing from the list.

Quick fixes by platform

Laravel

php artisan config:clear
php artisan cache:clear
php artisan view:clear

Note: cache:clear can log out all users if your sessions are stored in the cache.

Then confirm @csrf is in the form and your session settings are correct. See the Laravel CSRF docs.

Django. Confirm django.middleware.csrf.CsrfViewMiddleware is enabled, the template has {% csrf_token %}, and CSRF_TRUSTED_ORIGINS is correct. For pages where JavaScript posts without a form, use the ensure_csrf_cookie decorator.

WordPress. WordPress uses nonces ("number used once"), which are valid for a limited time (24 hours by default, per the WordPress nonce docs). Set your caching plugin's cache lifespan well under 12 hours, or exclude pages with forms or login. Then clear the cache in WP Rocket, W3 Total Cache, LiteSpeed Cache or your host's built-in cache. If a form plugin such as Contact Form 7, WPForms or Gravity Forms sits on a cached page, exclude that page's URL from page caching, or turn on the plugin's "refresh nonce" or AJAX token option if it has one.

Rails. For JavaScript requests, make sure the layout <head> includes csrf_meta_tags, and avoid fragment-caching forms. The Rails security guide explains the defaults.

What not to do

The internet is full of advice that "fixes" the error by removing the check. Avoid it.

  • Do not disable CSRF protection site-wide. It stops the error and removes the defense. A forged request can then change data using a visitor's login.
  • Do not exempt cookie-authenticated routes. Token-authenticated APIs that do not rely on browser cookies are a different case.
  • Only exempt webhook endpoints (for example from a payment provider) that verify their own signature instead. Keep that list short and review it.
  • Do not paste an old token into a form or hard-code one. It defeats the point.

If you have already turned protection off to "get things working", turn it back on and use the steps above. A free website security check can show whether other basics are in place, and our guide on whether your website is hackable explains what an attacker looks at first.

Key takeaways

  • A CSRF token mismatch means the token sent with the form did not match the one the server expected for that visitor.
  • Look at the failing request in DevTools first. It tells you whether the token, the cookie, or both are missing.
  • Frequent causes are expired sessions (in frameworks that store the token in the session, like Laravel and Rails), cached pages that serve a stale token, and cookie or domain mismatches (www vs non-www, HTTP vs HTTPS).
  • Exclude form pages from full-page caching, and purge your cache after changing the rules.
  • Never fix this error by turning CSRF protection off. Fix the cause instead.

Want a human to check the rest?

Fixing a form error gets your leads flowing again. It does not tell you whether the rest of your site is set up safely. A person can look at your forms, cookies, headers and logins together and write up what to fix. That is what a Bug Circuit manual audit does, for a one-time $49. It is a focused small-site audit, not a compliance certification. You can see what is included on the pricing page.

Want certainty, not guesswork?

A real human security engineer audits your whole site by hand and sends a full report: every issue, its severity, and the exact fix. From $49, with a 14-day money-back guarantee.

See pricing

Frequently asked questions

What does CSRF token mismatch mean?
It means the token submitted with your form did not match the one your server expected for that visitor. The server rejects the request to stop forged submissions. Real visitors usually hit it because their session expired, a cache served an old token, or their browser did not send the session cookie.
CSRF verification failed, how to fix it?
Open DevTools and check whether the token and session cookie are sent with the form. Then check for an expired session, a cached form page, a www vs non-www mismatch, and missing token tags in your template. In Django, also confirm CSRF_TRUSTED_ORIGINS includes the full scheme, such as https://www.example.com.
Invalid CSRF token, how to fix it on WordPress?
The cause is usually a cached page with an old nonce. Clear your cache, shorten your caching plugin's cache lifespan to well under 12 hours, and exclude login and form pages from caching. Also test with plugins disabled one by one to find any plugin that is rebuilding the form incorrectly.
Is it safe to disable CSRF protection to stop the error?
No. Disabling it removes the defense against forged requests that use a visitor's logged-in session. Fix the underlying cause instead. If you must exempt something, limit it to a webhook endpoint that verifies its own signature.
Why does my form work in one browser but show CSRF token mismatch in another?
The browsers may be handling cookies differently, for example through privacy settings, blocked third-party cookies, or an in-app browser. Check that the session cookie is set and sent back, and that your SameSite and Secure settings match how the form is loaded.

Keep reading

See what attackers see, for free

Run the free passive check on your website. No login, no impact on your site, results in seconds.

Ready for the full manual audit? See pricing

Published by Bug Circuit. Written with AI assistance and reviewed for accuracy before publishing.