# 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.

**Severity:** high | **Category:** Application Security

---

## The Issue

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.

## Steps to Fix

### 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. 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. 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. 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.

## Verification

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.

