Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

riccardo.t's avatar

Why am I getting a 419 response from a Laravel 8 CsrfMiddleware protected end point?

I've recently created a new Laravel 8 project following the instructions at the Laravel 8 . x sanctum docs page.

Now, according to this guide, CSRF protection should be used, meaning a call to <app_url>/sanctum/csrf-cookie, which will return an encrypted XSRF-TOKEN cookie . The value of this cookie should then be used to set the X-XSRF-TOKEN request header before every request .

My situation: I'm running Laravel locally in a Docker container and intend to use it as an API only; that is, I will have a Vue SPA talking to my Laravel backend. But for now, I'm testing the API using Insomnia. I expect this to work, it is essentially no different from an SPA .

I have one test route that is, by default, protected by CSRF middleware because it's in the api.php file:

    Route::prefix('v1')->group(function () {
    
        /**
         * get the health of the endpoint,
         * can be used to see if API is up and running
         */
        Route::post('health', function (Request $request) {
            return ['healthy' => true];
        });˜
    }

I can successfully call the /sanctum/csrf-cookie endpoint and receive the XSRF-COOKIE, so far so good. When I attempt to call the api/v1/health endpoint, I always get a 419 response.

A few relevant settings:

.env

    SESSION_DRIVER=cookie
    SESSION_DOMAIN=localhost
    SESSION_LIFETIME=480

cors.php

    'paths' => ['*'], //  ['api/*']
    'allowed_methods' => ['*'],
    'allowed_origins' => ['*'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => ['*'],
    'max_age' => 0,
    'supports_credentials' => true,

sanctum.php

    'stateful' => explode(',', env(
        'SANCTUM_STATEFUL_DOMAINS',
        'localhost,localhost:80,localhost:3000,localhost:8080,localhost:8085,127.0.0.1,127.0.0.1:8000,127.0.0.1:8085,::1'
    )),
    
    
    
    'middleware' => [
        'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
        'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
    ],

I've tried:

  • Disabling encryption of cookies in all relevant files
  • Entirely omitting the session domain from .env (so it becomes null in session.php)
  • Setting session domain to empty (SESSION_DOMAIN=) in .env
  • Setting X-CSRF-TOKEN header instead of X-XSRF-TOKEN header

Nothing seems to have any effect. Relevant request headers for the api/v1/health endpoint:

    X-XSRF-TOKEN=<XSRF-TOKEN-COOKIE-VALUE>
    Accept=application/json

Stack trace:

    {
      "message": "CSRF token mismatch.",
      "exception": "Symfony\Component\HttpKernel\Exception\HttpException",
      "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php",
      "line": 372,
      "trace": [
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php",
          "line": 317,
          "function": "prepareException",
          "class": "Illuminate\Foundation\Exceptions\Handler",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
          "line": 51,
          "function": "render",
          "class": "Illuminate\Foundation\Exceptions\Handler",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 172,
          "function": "handleException",
          "class": "Illuminate\Routing\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php",
          "line": 121,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php",
          "line": 63,
          "function": "handleStatefulRequest",
          "class": "Illuminate\Session\Middleware\StartSession",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Illuminate\Session\Middleware\StartSession",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php",
          "line": 37,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php",
          "line": 67,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Illuminate\Cookie\Middleware\EncryptCookies",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php",
          "line": 26,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 149,
          "function": "Laravel\Sanctum\Http\Middleware\{closure}",
          "class": "Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 103,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php",
          "line": 34,
          "function": "then",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 103,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
          "line": 695,
          "function": "then",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
          "line": 670,
          "function": "runRouteWithinStack",
          "class": "Illuminate\Routing\Router",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
          "line": 636,
          "function": "runRoute",
          "class": "Illuminate\Routing\Router",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
          "line": 625,
          "function": "dispatchToRoute",
          "class": "Illuminate\Routing\Router",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
          "line": 166,
          "function": "dispatch",
          "class": "Illuminate\Routing\Router",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 128,
          "function": "Illuminate\Foundation\Http\{closure}",
          "class": "Illuminate\Foundation\Http\Kernel",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
          "line": 21,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Illuminate\Foundation\Http\Middleware\TransformsRequest",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
          "line": 21,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Illuminate\Foundation\Http\Middleware\TransformsRequest",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php",
          "line": 27,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Illuminate\Foundation\Http\Middleware\ValidatePostSize",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php",
          "line": 86,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/fruitcake/laravel-cors/src/HandleCors.php",
          "line": 57,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Fruitcake\Cors\HandleCors",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/fideloper/proxy/src/TrustProxies.php",
          "line": 57,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Fideloper\Proxy\TrustProxies",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/spatie/laravel-http-logger/src/Middlewares/HttpLogger.php",
          "line": 28,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 167,
          "function": "handle",
          "class": "Spatie\HttpLogger\Middlewares\HttpLogger",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
          "line": 103,
          "function": "Illuminate\Pipeline\{closure}",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
          "line": 141,
          "function": "then",
          "class": "Illuminate\Pipeline\Pipeline",
          "type": "->"
        },
        {
          "file": "/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
          "line": 110,
          "function": "sendRequestThroughRouter",
          "class": "Illuminate\Foundation\Http\Kernel",
          "type": "->"
        },
        {
          "file": "/var/www/public/index.php",
          "line": 52,
          "function": "handle",
          "class": "Illuminate\Foundation\Http\Kernel",
          "type": "->"
        }
      ]
    }

Any idea what is going on here?

0 likes
7 replies
Snapey's avatar

how does your api know which session it is? are you sending the sanctum token or cookie?

riccardo.t's avatar

This is still before authentication, so there is no active session yet.

riccardo.t's avatar

My bad, according to the laravel docs I first need to request a csrf token before I can attempt to authenticate. I'll switch it around then.

riccardo.t's avatar

@snapey: it still does not work, even I switch authentication and obtaining the CSRF token. Authenticating fails with the same 419 error.

This is also true when I add the auth:sanctum middleware to the authentication endpoint, or to the health endpoint for that matter.

Snapey's avatar

You have to have sessions, else the server sees every request as a new one and cannot refer to previously issued csrf tokens

1 like
riccardo.t's avatar

Solved. The problem was two-fold.

Obviously I was using a session-based CSRF mechanism. This I did in combination with EnsureFrontendRequestsAreStateful middleware. Now, both the EnsureFrontendRequestsAreStateful and VerifyCsrfToken middleware failed because:

  • The EnsureFrontendRequestsAreStateful middleware did not recognize my requests from Insomnia as frontend requests. I fixed this by overriding the fromFrontend method:
    protected static function isEnvTesting(): bool
    {
        return in_array(config('app.env'), ['local', 'testing']);
    }


public static function fromFrontend($request): bool
    {
        return static::isEnvTesting() ? true : parent::fromFrontend($request);
    }

so that local/testing calls are always seen as frontend calls. This is not great for every use case, but it works okay for now.

  • The VerifyCsrfToken middleware gave a DecryptException stating "The payload is invalid". This happened to be because Insomnia does not automatically url-decode the value of the XSRF-TOKEN, as a JS framework would or could normally do. The consequence of that is that there is normally a = sign at the end of the base64 encoded payload, which is encoded into %3D. Because I don't feel like replacing this manually every time, I fixed it by overriding the getTokenFromRequest method in the VerifyCsrfToken custom middleware class:
protected function getTokenFromRequest($request)
    {
        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

        // Replace %3D by = in case the token was sent from
        // an application that does not url decode the xsrf-token
        // by default.
        $xsrf_token_header = str_replace('%3D', '=', $request->header('X-XSRF-TOKEN'));
        if (! $token && $header = $xsrf_token_header) {
            try {
                $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
            } catch (DecryptException $e) {
                $token = '';
            }
        }

        return $token;
    }

I hope it helps somebody... Thanks for the help @snapey.

Edit: Obviously it's better to use PHP's urldecode, e.g.:

$xsrf_token_header = urldecode($request->header('X-XSRF-TOKEN'));

The str_replace was just a proof of concept on my side.

Please or to participate in this conversation.