简体   繁体   中英

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.

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.

I also faced this error. I add this code on .env file and error is gone.

SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost

I had the same issue, tried to add the code below to.env

SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost

But it did not work however, changing localhost to 127.0.0.1 as below worked for me.

SESSION_DOMAIN=127.0.0.1
SANCTUM_STATEFUL_DOMAINS=127.0.0.1

You don't get a token mismatch with /api/me because this is a GET request, and CSRF protection is for endpoints that might perform an unauthorized command.

I think you might not (yet) grasp what CSRF is and what CRSF protection is supposed to do.

Here, just listing the user is not an unauthorized command; you can request it over and over again and nothing else happens. It does not change anything in a database, do a money transfer, reset a password or anything else.

Does that help think about why requesting any GET or HEAD does not involve CSRF checks?

Just requesting that URI itself shouldn't perform an unwanted command for an authorized user; traditionally only POST and others are acceptable for running some command on a Web application, API or not; there are a number of reasons why having all the parameters in a GET request is unacceptable, and the main one is leakage of potentially sensitive information.

If you check the documentation, it explicitly mentions that only POST, PUT, PATCH and DELETE are checked for the secret session value. The reasons why might need a bit more further reading.

[1] https://laravel.com/docs/8.x/csrf

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM