phplaravelcsrflaravel-sanctum

api endpoint not doing CSRF token validation on Sanctum - CSRF Token Mismatch


I am currently learning Laravel (which is not going particularly smoothly) and I have got a couple of routes configured to test authentication using sanctum.

I am building an API only laravel service with the plan that a ReactJS project will utilise the API.

I am currently though not using ReactJS and using Insomnia REST client to test the API.

I have a route for registering a new user, logging and then another route that just returns the authenticated user to prove that the authentication mechanism is working correctly.

I don't know too much about CSRF but my understanding is I request a new CSRF token and then for every request to the API this CSRF token is used, so for example when I login and then get the authenticated user from the corresponding route, the CSRF token cookie is also sent, and therefore if a different CSRF token is sent, I should get a token mismatch error.

I am testing this using Insomnia by sending a request to /sanctum/csrf-cookie which returns me back a 204 and Insomnia sets 3 cookies, one of which being an XSRF-TOKEN which I understand is an encrypted form of the CSRF token.

I then login successfully and then when I call my route to get the authenticated user, I modify or delete the XSRF-TOKEN cookie and send the request, when I would then expect to get an error about the token not matching but this doesn't seem to be the case and I get a valid response back.

Below is my api.php (I'm grouping various routes into separate PHP files to keep things organised when I come to actually building the API)

Route::prefix('/auth')->group(__DIR__ . '/endpoints/auth.php');

Route::middleware('auth:sanctum')->get('/me', function(){
    //return response(null, 200);
    return auth()->user();
});

In my /endpoints/auth.php I have the following:

Route::post('/register', [UserController::class, "register"]);

Route::post('/login', [UserController::class, "login"]);

Route::middleware('auth:sanctum')->post('/logout', [UserController::class, 'logout']);

So in the code above, when I send a request to /api/me after changing or deleting my XSRF-TOKEN I would expect the token mismatch but I am actually getting a 200 OK with the authenticated user details.

UPDATE

I've managed to make some progress.

I've added the following items to the App/Http/Kernel.php under the api array as follows:

'api' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

When I attempt to submit the login request I now get an HTTP 419 with the error CSRF token mismatch.

So I've made progress that it now seems to be attempting the CSRF validation, but now it always says there's a mismatch even though its sending the same XSRF-TOKEN cookie in the request.


Solution

  • I believe I have figured it out, it was partly to do with my HTTP Rest client (Insomnia.Rest) but I set up a test project using axios on ReactJS and was having the same issue but then resolved it.

    Part of it was because Sanctums default configuration is a bit all of over the place, part of it was encrypting session/cookies and the other part wasn't.

    So under config/session.php set encrypt => true

    Under App\Http\Kernel.php add the following to the api middlewareGroups

    \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    

    Under confif/cors.php set support_credentials => true

    Under config/session.php ensure SESSION_DRIVER is cookie

    The other problem was a misunderstanding of what gets sent in the request.

    When you get the XSRF-TOKEN when calling /sanctum/csrf-cookie I assumed that is just sent back in the request as a cookie which Insomnia automatically does.

    This is not the case. Instead, you should extract the cookie from the Insomnia request, and then in each POST/PUT/DELETE request add a new header called X-XSRF-TOKEN to the value of what the cookie was from the sanctum GET request.

    If you are using an HTTP client for your frontend project such as AXIOS this is done automatically so you don't need to worry about that.

    The next issue was with Insomnia.Rest HTTP client I was using.

    When I received the XSRF-TOKEN Insomnia stores the cookie inside it cookie store, however, they seem to encode it incorrectly so you get a cookie string stored as follows:

    eyJpdiI6Iml5YWEreGVaYUw0WGc2QmxlVEhQOGc9PSIsInZhbHVlIjoieVU2bmdyTjMyNFM0d0dnb3RsM24rMDFhRnJNWHVLcGg2SU9YMHh5dW8yaTZSTWcxbGxtSFdaK0I5MzB4Ymc4QWZWSzhjN2R6Y1RUTTc0d1VIY2FUaVhGMVE4bzQvWVBmL1YvajAwY3ZUNlZ4VEZIRk12cloyV0owVmNYOUxEZTIiLCJtYWMiOiI4OTUyN2U1MGI3NmUyMjEzZjgyNDcxMjAwYmViYjRkNzAwYmQ1YWUxOGY5NTYyNTVhZDczMmQ0ZjdlNjQwMGFhIn0%3D

    Note at the end it has %3D, this is a URL encoding for the = sign so therefore Laravel gets this and can't match with what it was expecting.

    Therefore you need to edit the cookie to replace %3D to be = and then send the request and it should work.

    I have one other strange thing though is that if I send the request with a Referrer and Origin header, the CSRF validation works, however, if I don't then the request is accepted which doesn't seem right to me as it kind of defeats the purpose of the CSRF protection.