Fix Missing CSRF Protection in Laravel: @csrf, VerifyCsrfToken & API Routes

Laravel forms without @csrf tokens are vulnerable to cross-site request forgery. Learn how to add CSRF protection, configure VerifyCsrfToken exceptions, and handle CSRF for API routes.

High severity Application Security Updated 2026-03-01 Markdown

Cross-Site Request Forgery exploits a fundamental characteristic of how browsers work: cookies are sent automatically with every request to the associated domain, regardless of which site initiated the request. This means if a user is logged in to your application and visits a malicious site, that site can silently make HTTP requests to your application — and the browser will helpfully attach the session cookie, making the request appear to come from the authenticated user.

The attack predates Laravel by decades and has been exploited in countless real-world incidents. In 2008, a CSRF vulnerability in major router firmware allowed attackers to change DNS settings for millions of home users by tricking them into visiting a malicious page. Banking applications have been targeted to initiate unauthorized transfers. Social platforms have been exploited to post content without user consent. Any state-changing action a user can perform through a browser is a potential CSRF target.

Laravel's VerifyCsrfToken middleware addresses this by requiring a synchronized token — a secret generated server-side and embedded in forms — to accompany every state-changing request. The token is bound to the user's session and rotated regularly. The introduction of the SameSite cookie attribute in modern browsers provides an additional browser-native CSRF defense, but not all environments implement it fully, and it doesn't protect all request types. CSRF tokens remain a required defense layer in any application that uses session-based authentication.

The Problem

Missing CSRF protection allows attackers to trick authenticated users into submitting malicious requests to your application. Without CSRF tokens, a malicious website can create a hidden form that submits a POST request to your Laravel app, performing actions like changing passwords, transferring funds, or deleting accounts on behalf of the logged-in user without their knowledge.

How to Fix

  1. 1

    Add @csrf to all forms

    Every HTML form that submits POST, PUT, PATCH, or DELETE requests must include the @csrf directive:
    <form method="POST" action="/profile">
        @csrf
        @method('PUT')
        <input type="text" name="name" value="{{ $user->name }}">
        <button type="submit">Update Profile</button>
    </form>

    The @csrf directive generates a hidden input with the CSRF token:

    <input type="hidden" name="_token" value="random-token-here">
  2. 2

    Configure CSRF for AJAX requests

    Add the CSRF token meta tag to your layout's <head>:

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

    Then configure your JavaScript HTTP client to send it automatically. With Axios (already configured in Laravel):

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

    With fetch:

    fetch('/api/endpoint', {
        method: 'POST',
        headers: {
            'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
    });
  3. 3

    Verify the CSRF middleware is active

    In Laravel 11+, CSRF protection is enabled by default. In earlier versions, check that VerifyCsrfToken is in your middleware stack in app/Http/Kernel.php:

    protected $middlewareGroups = [
        'web' => [
            // ...
            \App\Http\Middleware\VerifyCsrfToken::class,
        ],
    ];

    Do not add routes that modify data to the $except array in VerifyCsrfToken unless they are webhook endpoints with their own authentication.

  4. 4

    Handle CSRF for SPA and API routes

    For single-page applications using Laravel Sanctum, initialize the CSRF cookie before making requests:

    // Call this once before authenticated requests
    await axios.get('/sanctum/csrf-cookie');
    // Now you can make authenticated requests
    await axios.post('/api/user/profile', data);

    API routes using token authentication (not session-based) do not need CSRF protection as they are not vulnerable to CSRF attacks.

How to Verify

Submit a form without the CSRF token and verify you receive a 419 Page Expired response. You can test with curl:
curl -X POST https://yourdomain.com/login -d "email=test@test.com&password=test"

This should return a 419 status code, confirming CSRF protection is active.

Prevention

Use Blade components and Livewire for forms, which handle CSRF automatically. Never remove the VerifyCsrfToken middleware from the web middleware group. Code review all form submissions to ensure @csrf is present. Use StackShield to verify CSRF protection is active on your public forms.

Frequently Asked Questions

Why do I get 419 Page Expired errors?

The 419 error means the CSRF token is missing or expired. Common causes: the form is missing @csrf, the user session expired, your session driver is misconfigured, or the token was not included in AJAX requests. Regenerate the token by refreshing the page.

Should I exclude webhook routes from CSRF protection?

Yes, webhook endpoints from third-party services (Stripe, GitHub, etc.) cannot include CSRF tokens. Add them to the $except array in VerifyCsrfToken and verify webhooks using their own signature mechanism instead, such as Stripe's webhook signature verification.

Does SameSite=Strict make CSRF tokens unnecessary?

Not entirely. SameSite=Strict provides strong CSRF protection in modern browsers, but it has gaps: it doesn't protect on the first request if the cookie hasn't been set with SameSite yet, older browsers don't support it, and there are edge cases in cross-site navigation flows. Defense in depth means keeping both CSRF tokens and SameSite cookies.

How do I handle CSRF with a Vue.js or React SPA frontend?

For SPAs served from the same domain, add the CSRF token to a meta tag and configure your HTTP client (Axios, fetch) to send it as the X-CSRF-TOKEN header with every request. For SPAs on a different domain using Sanctum, call /sanctum/csrf-cookie first to initialize the CSRF cookie, then proceed with authenticated API requests — Sanctum handles the token via the XSRF-TOKEN cookie.

Are API routes under api/ protected by CSRF by default?

No. In Laravel, routes defined in routes/api.php use the api middleware group, which excludes VerifyCsrfToken. This is intentional: API routes are expected to use token-based authentication (Sanctum, Passport, JWT) rather than sessions, making them not vulnerable to classic CSRF. If your API uses session-based authentication, you should add CSRF protection explicitly.

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 60 seconds.