7

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 it's sending the same XSRF-TOKEN cookie in the request.

11
  • Check that you're not sending the token via other sources like the _token form field of the X-CSRF-Token header Commented Jun 14, 2021 at 13:28
  • @apokryfos sorry not sure I follow, I'm not sending from a form as I'm using a REST test client, there is no body data being sent in the request Commented Jun 14, 2021 at 13:29
  • I'm not sure if you'd get CSRF protection by default for API routes since it works via the session. Check your VerifyCsrfToken middleware if api routes are excluded (they generally should be). Try moving your routes in web.php if you're using the session or don't use CSRF tokens if you're using Laravel as an API Commented Jun 14, 2021 at 14:05
  • I thought Laravel APIs (or APIs in general) should have CSRF protection regardless. Sanctum is using token based authentication currently. There's nothing in the except array in VerifyCsrfToken, its just an empty array. If I move my routes to the web.php isn't that more for Laravel frontend so I then wouldn't be using the /api path? Commented Jun 14, 2021 at 14:27
  • CSRF is not usually needed (or even possible) for APIs since because by their nature all requests are considered cross-site. Cookies are not typically expected to be stored and transmitted when reading from an API since cookies are more of a browser thing and it takes extra setup for rest clients to actually store them correctly Commented Jun 14, 2021 at 14:33

5 Answers 5

16

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.

Sign up to request clarification or add additional context in comments.

2 Comments

"it kind of defeats the purpose of the CSRF protection" it's because CSRF token are not meant to be used to protect API calls, it is meant to protect against form forgery/Cross Site attacks. You can use Origin header, JWT (JsonWebToken) for extra protection, many things that are stateless. coockies and CSRF are for statefull calls.
A big thank you for the Insomnia %3D "fix". I was starting to question my own sanity.
6

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

SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost

2 Comments

php artisan config:cache afterwards if you are still getting an error
Thanks, it worked for me; I was trying using Laravel Herd. disabled it and ran the Laravel script using php artisan:serve
3

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

Comments

0

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

Comments

0

I was getting 401 unauthenticated message from 'auth:sanctum' middleware.

The solution was to add the Origin header with the same value as the FRONTEND_URL variable.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.