
8 minutes read
The "419 Page Expired" error in Laravel: 5 quick fixes
Table of contents
Introduction
Ever hit the frustrating “419 Page Expired” error in your Laravel app? It commonly disrupts form submissions, logins, and other POST requests tied to CSRF protection and session state. I regularly diagnose 419s for teams, and the checklist below resolves most cases quickly.
Note: 419 Page Expired is a non‑standard status code that Laravel uses for CSRF token mismatches or expired/rotated sessions; it is not an IANA HTTP status. See the list of HTTP status codes for context.
TL;DR: five quick fixes
- Ensure every form includes
@csrf
. - If you were idle, refresh the page to get a fresh session token; this works as designed.
- For AJAX, make sure the request sends
X-CSRF-TOKEN
orX-XSRF-TOKEN
. Axios often handles this automatically on same‑origin requests. - Verify session and cookie config: lifetime,
same_site
,secure
, andSESSION_DOMAIN
aligned to your domain scheme. - If needed for webhooks, exclude routes from CSRF checks using
validateCsrfTokens
in Laravel 11. Details below.
Quick checklist (snippet‑friendly): check @csrf
, then your session driver, SESSION_DOMAIN
, and whether cookies are sticking across requests. This mirrors common advice in the long‑running Stack Overflow thread. See the troubleshooting checklist for a deeper pass.
Why a 419 happens in Laravel
CSRF token and session basics
Laravel generates a CSRF token per user session and verifies it on state‑changing requests. The token is tied to the session; a so‑called “token expired” situation usually means the session expired or was regenerated, which causes a mismatch until the page is refreshed. See Laravel’s CSRF documentation.
Common root causes
- Missing
@csrf
on forms. - Session expired or regenerated.
XSRF-TOKEN
cookie present but the request header is missing on AJAX.SESSION_DOMAIN
misconfigured or cross‑subdomain usage without a proper cookie domain.- Mixing HTTPS and HTTP between page load and request.
Fixes in detail
Forms
Always include the directive in Blade forms:
<form method="POST" action="/submit"> @csrf <input type="text" name="name" /> <button type="submit">Send</button> </form>
If users step away for a while and the session expires or rotates, ask them to refresh the page to get a fresh token; it works as designed.
AJAX and SPAs with Sanctum
Handling AJAX requests properly
Laravel sets an XSRF-TOKEN
cookie, and many libraries automatically send X-XSRF-TOKEN
on same‑origin requests. You can also send the token explicitly with X-CSRF-TOKEN
from a meta tag.
<meta name="csrf-token" content="{{ csrf_token() }}" />
fetch('/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ example: 1 }) });
Notes for Axios/jQuery users:
- On same‑origin requests, Axios typically reads
XSRF-TOKEN
and sendsX-XSRF-TOKEN
automatically; jQuery can do similar with appropriate setup. You usually do not need to wire this header manually. - If a library expects a decoded value, URL‑decode the cookie before sending (for example,
decodeURIComponent
). See Laravel CSRF docs.
SPAs with Sanctum
For SPA authentication with cookies, initialize CSRF protection, then log in. Ensure cookie domain and SameSite settings match your architecture. Axios can send the header automatically on same‑origin requests.
// Initialize CSRF protection, then perform login await axios.get('/sanctum/csrf-cookie'); await axios.post('/login', { email, password });
If your SPA is on a different subdomain, enable credentials and set cookie/domain config accordingly:
axios.defaults.withCredentials = true;
See Laravel Sanctum: SPA authentication and the CSRF docs.
Session and cookie configuration
Check reasonable defaults in config/session.php
and align them to your environment:
'lifetime' => env('SESSION_LIFETIME', 120), // minutes 'expire_on_close' => false, 'http_only' => true, // HttpOnly 'secure' => env('SESSION_SECURE_COOKIE', true), 'same_site' => env('SESSION_SAME_SITE', 'lax'), // 'lax', 'strict', or 'none' 'domain' => env('SESSION_DOMAIN', null),
Practical notes:
- If
SameSite
is set tonone
, the cookie must also beSecure
and the site must be served over HTTPS. Browsers otherwise reject the cookie. See this reminder and discussion in community answers and docs. - Avoid setting
SESSION_DOMAIN
to an IP or a mismatched domain during development; leave itnull
unless you specifically need cross‑subdomain sharing. Browsers treat the cookie Domain attribute strictly. See MDN’s overview of cookies.
Server configuration for Apache and Nginx
Correct web server routing prevents Laravel from bypassing public/index.php
.
Apache: set your VirtualHost DocumentRoot
to the application’s public
directory and use Laravel’s default public/.htaccess
with file/dir checks. Minimal rewrite section:
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.php [L] </IfModule>
Nginx: a minimal, copy‑pasteable server block that points root
to /public
, uses try_files
, and forwards PHP to FPM:
server { listen 80; server_name example.com; root /var/www/your-app/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php/php8.2-fpm.sock; # adjust to your PHP-FPM socket or host:port fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; } location ~ /\.[^/]+ { deny all; } }
These patterns mirror widely used examples from community guides and the default Laravel .htaccess
.
Laravel 11 vs 10 and earlier: excluding routes from CSRF
When you must skip CSRF verification for specific endpoints like webhooks, prefer a targeted exclude.
Laravel 11:
// bootstrap/app.php use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) ->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: [ 'webhook/*', ]); }) ->create();
Laravel 10 and earlier:
// app/Http/Middleware/VerifyCsrfToken.php class VerifyCsrfToken extends Middleware { protected $except = [ 'webhook/*', ]; }
See Laravel’s CSRF docs for details.
Troubleshooting checklist
- Forms:
@csrf
present in every POST/PUT/PATCH/DELETE form; method spoofing is correct. - Session driver: confirm a stable driver (
file
,redis
, etc.) and that sessions persist across requests. - Cookies: verify
XSRF-TOKEN
andLARAVEL_SESSION
originate from the correct domain and path; confirmSecure
andSameSite
fit your setup. - AJAX: confirm your request includes
X-CSRF-TOKEN
orX-XSRF-TOKEN
and that the browser is sending cookies. - Domains: avoid
SESSION_DOMAIN
mismatches and IP‑based domains that prevent cookies from sticking. - Protocol: do not mix HTTP and HTTPS between page load and API calls.
Additional troubleshooting tips
- Use Artisan to clear build‑time caches that can confuse local testing:
php artisan optimize:clear php artisan cache:clear php artisan config:clear php artisan view:clear
- Quick server‑side session sanity check:
Route::get('/session-test', function () { session(['test' => 'it works!']); return session('test'); });
- Review Laravel’s logs in
storage/logs/laravel.log
.
FAQ
-
Is 419 a real HTTP status code? No. It is a non‑standard status used by Laravel to indicate a CSRF mismatch or expired session. See the HTTP status code list.
-
How do I fix 419 on Axios or fetch? For same‑origin requests, Axios usually sends
X-XSRF-TOKEN
automatically from theXSRF-TOKEN
cookie. Otherwise, setX-CSRF-TOKEN
from a meta tag or decode and send the cookie yourself. For cross‑subdomain with Sanctum, call/sanctum/csrf-cookie
and enable credentials. See Laravel CSRF and Sanctum. -
How do I disable CSRF for webhooks in Laravel 11? Use
validateCsrfTokens(except: [...])
inbootstrap/app.php
. See the code and the CSRF docs. -
Why do I get 419 after being idle? Your session likely expired or was regenerated, which invalidates the token until you refresh the page. See CSRF protection.
-
Why does 419 happen on subdomains or IPs? Cookies may not be sent if
SESSION_DOMAIN
is misconfigured or if you are testing on an IP where cookie domain rules differ. Browsers enforce cookie domain attributes strictly. See MDN on cookies. -
Why does the console show “Unknown status 419”? Some tools label non‑standard statuses as “Unknown.” It is still the same CSRF/session issue described here.
-
Do I need both
X-CSRF-TOKEN
andX-XSRF-TOKEN
? No. Send one: eitherX-CSRF-TOKEN
from a meta tag orX-XSRF-TOKEN
derived from theXSRF-TOKEN
cookie. See Laravel CSRF docs.
Conclusion
The Laravel 419 error is almost always a solvable CSRF or session configuration issue. Start with @csrf
, refresh if idle, confirm the AJAX header, validate session and cookie settings, and only then consider route exclusions. My preferred order is forms → AJAX headers → session and cookie settings → server routing → targeted CSRF excludes. For deeper reference, see Laravel’s official CSRF protection documentation and Sanctum docs.
Did you like this article? Then, keep learning:
- Related Laravel error fix deepening troubleshooting knowledge
- Add Alpine.js to enhance Laravel frontend interactivity
- Learn how to clear Laravel's cache, complementing session fixes
- Explore Laravel middleware customization relevant to CSRF middleware
- Understand Laravel's error handling for robust HTTP requests
- Understand Laravel basics to contextualize CSRF token workings
- Upgrade guides to keep Laravel projects updated and secure
- Upgrade guides continue, useful for Laravel CSRF updates
- Discover Laravel security best practices related to CSRF protection
- Learn Artisan, useful for cache clearing and debugging Laravel apps
12 comments
Hi @Benjamin, how we replace the error page 419 with notification? I want make the error popup more nice than default laravel error page.
Never thought about doing that. Worth asking ChatGPT because I have no idea where to start.
What if the 419 appears just for me? All my colleagues can log in from theirs house's, but I can't even though I have restarted my network modem...
This error has nothing to do with your location or your network. 🙂
There might be something related to your cookies, your browser possibly restricts cookies.
As Benjamin said:
So, please check your browser configuration and see if there's something odd, or try to delete the old cookie, this might solve your problem.
I'm facing same 419 page expired issue but mine is a Laravel 11 API, I'm using postman and understand I have to make a pre-request to /sanctum/csrf-token for the x-xsrf-token, but that request returns an empty response and I'm not sure what to try now. the documentation does not touch on this for APIs.
Have you tried following the documentation from the start again? I find it weird that
/sanctum/csrf-token
has no effect. What's the HTTP code? 419 there too?Also, when you run
php artisan route:list
you will see which controller is associated with/sanctum/csrf-token
. Maybe put add()
there. That's how you make sure you're actually hitting the route.Thanks. 419 issue resolved after adding /login page in protected list of VerifyCsrfToken
That's great, but I don't recommend that. A login page must be secure. 🙂
ok. got you. Seems to be a necessary evil.
@BenjaminCrozat - I wonder what the best practice is for reducing 419 on login pages. I'm using L11 JetStream (rip) default scaffolding and experience this. Seems like refreshing the csrf token with JS in browser might be only solution... wonder if this get's solved in L12/StarterKits
Sorry for the late reply! This hasn't been solved. You can increase the session's lifetime, though. I updated the article since you commented.