Laravel CSRF Protection Explained – Tokens, Middleware & AJAX Guide

Himmat Regar Jun 2, 2025, 11:53 PM
Laravel
Views 1160
Blog Thumbnail

1. What is CSRF?

Cross-Site Request Forgery (CSRF) is an attack that tricks a logged-in user’s browser into sending unwanted requests to your application.
If you don’t defend against it, an attacker can:

  • change a user’s email

  • transfer money

  • delete records

  • …anything the victim is authorised to do

The trick? The malicious request comes from the victim’s own browser, so it passes session authentication unless you have an extra “secret” check.


2. How Laravel solves the problem

Laravel’s built-in middleware VerifyCsrfToken adds a unique, random token to every active session:

  1. Token generated and stored in the session.

  2. The token is embedded into every HTML <form> you render with the @csrf Blade directive (or the helper csrf_field()).

  3. On POST, PUT, PATCH, or DELETE requests, the middleware compares the submitted token to the one saved in the session.

  4. If they match ⇒ request continues.
    If they don’t ⇒ 419 | Page Expired (token mismatch).

Good news: for typical Blade forms you get 100 % protection by default—just remember @csrf.


3. Quick example – Classic Blade form

Route & controller

// routes/web.php
Route::post('/profile/email', [ProfileController::class, 'updateEmail'])
     ->name('profile.email');

 

 
 
// app/Http/Controllers/ProfileController.php
public function updateEmail(Request $request)
{
    $request->validate([
        'email' => ['required', 'email'],
    ]);

    $request->user()->update([
        'email' => $request->email,
    ]);

    return back()->with('success', 'Email updated!');
}

 

View

<!-- resources/views/profile/email.blade.php -->
<form action="{{ route('profile.email') }}" method="POST">
    @csrf               <!-- 👈 inserts hidden _token input -->
    <label>Email</label>
    <input type="email" name="email" value="{{ old('email', auth()->user()->email) }}">
    <button type="submit">Save</button>
</form>

 

 

No further work needed—the CSRF middleware will verify the token automatically.


4. CSRF with AJAX or SPA front-ends

When you post via JavaScript you must send the token manually.
Laravel exposes it in a cookie named XSRF-TOKEN and in Blade you can also print it into a meta tag:

<meta name="csrf-token" content="{{ csrf_token() }}">

 

 

Axios example

import axios from 'axios';

// 1) read from meta tag
axios.defaults.headers.common['X-CSRF-TOKEN'] =
    document.querySelector('meta[name="csrf-token"]').getAttribute('content');

// 2) normal request
axios.post('/profile/email', { email: 'new@example.com' })
     .then(res => console.log(res.data))
     .catch(err => console.error(err));

 

 

Why not fetch from the XSRF-TOKEN cookie?
Browsers block JavaScript from reading http-only cookies.
Hence the Blade meta approach, or pull it from the DOM with @json(csrf_token()).


5. Excluding (or including) URIs

Sometimes you need to disable CSRF for specific endpoints—e.g. a public webhook.

Edit app/Http/Middleware/VerifyCsrfToken.php:

protected $except = [
    '/stripe/webhook',
    'https://trusted-service.com/*',
];

 

 

Conversely, if you’re building an API that uses tokens (Laravel Sanctum, Passport, etc.) you’ll often remove the CSRF middleware from your api middleware group entirely.


6. Regenerating tokens after login / logout

Laravel automatically regenerates the session ID and CSRF token when:

  • a user logs in (LoginController)

  • a user logs out (LogoutController)

This prevents session fixation and token reuse attacks.


7. Common pitfalls & fixes

Issue Cause Fix
**419 Page Expired** on POST Token missing or wrong
Token mismatch only in Firefox or Safari Mixed domain/sub-domain cookies Set identical domain on session cookie or disable SESSION_DOMAIN in .env
Testing fails with 419 Token not included in test In Feature tests, call withSession(['_token' => csrf_token()]) or just use $response = $this->post(route(...), $data) which automatically sets it
SPA with Inertia / Livewire Token out of sync after login via XHR Refresh token from cookie/meta after axios.post('/login') succeeds

8. Writing Feature tests with CSRF

public function test_email_update_is_protected()
{
    $user = User::factory()->create();
    $this->actingAs($user);

    // Send request WITHOUT token
    $response = $this->post('/profile/email', [
        'email' => 'evil@example.com',
    ]);

    $response->assertStatus(419);
}

public function test_email_update_success()
{
    $user = User::factory()->create();
    $this->actingAs($user);

    // Send request WITH token (auto-added)
    $response = $this->post('/profile/email', [
        'email' => 'new@example.com',
        '_token' => csrf_token(),
    ]);

    $response->assertRedirect();
    $this->assertEquals('new@example.com', $user->fresh()->email);
}

 

 

Laravel’s test helpers handle tokens automatically unless you pass a custom $headers array—then you must include it yourself.


9. Advanced: Changing the default header name

If you prefer another header (e.g. X-XSRF-TOKEN instead of X-CSRF-TOKEN) add in AppServiceProvider:

use Illuminate\Support\Facades\URL;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

public function boot()
{
    Middleware::$header = 'x-xsrf-token';
}

 

 

10. Key take-aways

  • Blade forms: always include @csrf.

  • JS/AJAX: send the token via X-CSRF-TOKEN header.

  • Don’t disable CSRF unless you have a very good reason.

  • Regenerate tokens on sensitive auth events.

  • Test both protected & unprotected flows.


✨ That’s it! You now have a full understanding of Laravel’s CSRF protection and how to use it safely in traditional Blade apps, SPAs, and APIs.

💡 Frequently Asked Questions (FAQ) about Laravel CSRF Protection

# Question Short Answer
1 What exactly is the _token field that Blade inserts? A hidden form input containing the CSRF token stored in the user’s session. Laravel’s middleware checks it on every state-changing request.
2 Do I need CSRF tokens on GET routes? No. Only methods that modify state (POST, PUT, PATCH, DELETE) are checked. GET requests should remain idempotent, so CSRF is not validated there.
3 **Why do I get “419 Page Expired” even though I added @csrf?**
4 How do I send the token in Axios or Fetch? Read it once from the <meta name="csrf-token"> tag and set it on every request header: axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').content;.
5 What’s the difference between X-CSRF-TOKEN and X-XSRF-TOKEN? X-CSRF-TOKEN is what you set in JS. XSRF-TOKEN is a cookie Laravel sets so that frameworks like Angular or Axios can automatically read and forward it (if you enable that feature).
6 Can I disable CSRF for a single webhook URL? Yes. Add the URI to the $except array inside app/Http/Middleware/VerifyCsrfToken.
7 Is CSRF protection required for APIs that use Sanctum or Passport? If you’re using token-based auth (the mobile-app style), you usually remove the CSRF middleware from api routes. If you use Sanctum’s SPA mode (cookie-based), keep CSRF enabled.
8 Does Livewire handle CSRF for me? Yes. Livewire’s request payload automatically includes the token, so no manual work is needed.
9 Should tokens be regenerated on every request? No. Laravel regenerates the token on login/logout and when the session ID changes. That’s sufficient; regenerating every request would break concurrent tabs.
10 How do I test CSRF-protected routes in PHPUnit? Use the built-in HTTP helpers ($this->post(...)) — they automatically attach a valid token when the test is run inside a session established with actingAs().

Still stuck?

  • Debug tip: Dump the current token in your view ({{ csrf_token() }}) and compare it with the one arriving in Request::header('x-csrf-token').

  • Local vs production: On localhost you can set appVerificationDisabledForTesting, but never disable CSRF middleware in production.

 

Comments

Please login to leave a comment.

No comments yet.

Related Posts

MVC-Architecture-in-Web-Development
217 viewsLaravel
Himmat Kumar • Feb 18, 2025, 12:23 PM

MVC Architecture in Web Development

laravel-framework-overview
231 viewsLaravel
Himmat Kumar • Jul 29, 2024, 11:15 AM

Laravel Framework Overview: Features, Benefits, and Res...

laravel-setup-tutorial
288 viewsLaravel
Himmat Kumar • Jul 30, 2024, 12:03 PM

Master Laravel: Basic System Requirement,Installation a...

how-to-send-emails-with-queues-in-laravel
146 viewsLaravel
Himmat Kumar • Oct 16, 2024, 12:32 PM

How to Send Emails with Queues in Laravel. how to use ...

laravel-application-structure
183 viewsLaravel
Himmat Kumar • Dec 3, 2024, 8:33 AM

Laravel Application File Structure

laravel-configuration
226 viewsLaravel
Himmat Kumar • Dec 4, 2024, 11:58 AM

Laravel Configuration

what-is-laravel-routing
223 viewsLaravel
Himmat Kumar • Dec 6, 2024, 8:29 AM

What is laravel routing

get-route-in-laravel
198 viewsLaravel
Himmat Kumar • Dec 13, 2024, 11:07 AM

GET Route in Laravel

laravel-post-route-and-post-request-post-request-using-postman
199 viewsLaravel
Himmat Kumar • Dec 14, 2024, 8:32 AM

laravel post route and post request , post request usin...

What-is-laravel-controller
308 viewsLaravel
Himmat Kumar • Dec 24, 2024, 12:22 AM

What is Laravel - Controllers