laravel / sanctum

Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.
https://laravel.com/docs/sanctum
MIT License
2.76k stars 296 forks source link

EnsureFrontendRequestsAreStateful not working properly #229

Closed ericwang401 closed 3 years ago

ericwang401 commented 3 years ago

Description:

The middleware, EnsureFrontendRequestsAreStateful, is not properly authorizing the cookies in the request.

I have to use the web middleware along with api middleware in order for it to properly authorize. image This is not ideal to me.

I have already added the middleware in the kernel: image

I have already added the env variables (AND cleared config cache AND rebooted my VPS): image

Other information:

Steps To Reproduce:

  1. Download the repository from https://github.com/AnushK-Fro/Stratum
  2. Install the Composer and NPM packages
  3. Setup a Mariadb database
  4. Use the env file template here: https://hastebin.com/azujavevug.ini
  5. Confirm you have SSL enabled AND you are hosting this on a subdomain (for accurate reproduction)

Creating a user:

  1. Run php artisan tinker
  2. Run Stratum\Models\User::create(['name' => 'test', 'email' => 'test@test.com', 'password' => Hash::make('password')])

Setting up Postman environment:

  1. Click on the eye icon next to No Environment image
  2. Add a new environment image
  3. Configure it like this (CHANGE THE HOST VARIABLE TO YOUR HOST) image
  4. Create a new request with the URL https://<host>/api/login (Replace with your host)
  5. Make sure your headers look like this image
  6. Add the form-data in the body image
  7. Click the pre-request script tab and paste this (REPLACE WITH YOUR HOST) pm.sendRequest({ url: 'https://<host>/sanctum/csrf-cookie', method: 'GET' }, function (error, response, { cookies }) { if (!error) { pm.environment.set('xsrf-token', cookies.get('XSRF-TOKEN')) } })
  8. Click send and it should return (Warning: if you already authenticated, fortify WILL return 404 error if you try to authenticate again) image
  9. Copy the template (headers, pre-request script) and use https://<host>/api/client/wow for your URL (replace with your host)
  10. Click send and it will return { "message" : "unauthenticated." }

Workaround for this issue

  1. Navigate to app\Providers\RouteServiceProvider.php
  2. Add the 'web' middleware to the Route::prefix('/api/client') route (MAKE SURE 'web' IS FIRST IN THE ARRAY)
  3. Click send on Postman & Laravel will authorize the request and return test
driesvints commented 3 years ago

Hey there,

Can you first please try one of the support channels below? If you can actually identify this as a bug, feel free to report back and I'll gladly help you out and re-open this issue.

Thanks!

ericwang401 commented 3 years ago

I have already visited and posted my issue on the Laravel Discord, Stackoverflow, and Laracasts Forums.

I believe this is a bug because I have to use the web middleware. BUT I already added EnsureFrontendRequestsAreStateful middleware to the api middleware group in app\Http\kernel.php (you can see this in the Github repo: https://github.com/AnushK-Fro/Stratum)

ericwang401 commented 3 years ago

Oh ok. Could the documentation be updated to be slightly more clear?

I believed from the documentation that I could simply add the middleware to the api group and have Laravel Sanctum authorize the API routes.

Because from this tutorial (https://youtu.be/uPKd3q-iaVs) and several other Stackoverflow threads, I saw no instances of the web middleware included. To add onto that, the tutorial I mentioned earlier doesn't use the web middleware and explains at 1:37 that EnsureFrontendRequestsAreStateful is similar to the web middleware group. From this, I inferred I could simply use the api middleware without having to add the web middleware group.

The repository used in the tutorial is here: https://github.com/ggelashvili/nextjs-spa-auth-laravel-sanctum-backend/

If you open up the RouteServiceProvider file (https://github.com/ggelashvili/nextjs-spa-auth-laravel-sanctum-backend/blob/master/app/Providers/RouteServiceProvider.php), there is no web middleware included in the API routes.

And in the api route file (https://github.com/ggelashvili/nextjs-spa-auth-laravel-sanctum-backend/blob/master/routes/api.php), there is no web middleware included.

Finally, in the kernel in app\Http (https://github.com/ggelashvili/nextjs-spa-auth-laravel-sanctum-backend/blob/master/app/Http/Kernel.php), the tutorial only used EnsureFrontendRequestsAreStateful middleware.

Conclusion

If I have to use the web middleware instead of the middleware provided by Sanctum, can the documentation be updated to reflect that EnsureFrontendRequestsAreStateful will not authorize cookies used on the API routes?

I don't want people to spend hours (or days) searching for a solution to find that EnsureFrontendRequestsAreStateful does not authorize API routes and they have to use the web middleware.

Thanks!

ericwang401 commented 3 years ago

I tried this on a fresh installation of Laravel with Fortify + Sanctum, and I reproduced the same problem on my local development environment (not a VPS).

(Routes are located in routes/api.php) https://drive.google.com/drive/folders/10rWjrv9hOL0dfQ5U7QSKLcSVc68pHe9N?usp=sharing

Also, in the installation (link), EnsureFrontendRequestsAreStateful was explicitly stated in the documentation to be used to authenticate SPAs.

Can this issue be reopened?

ericwang401 commented 3 years ago

@driesvints Hey can this issue be reopened?

Thanks!

driesvints commented 3 years ago

Can you post the link to the laracasts forum post of your issue?

ericwang401 commented 3 years ago

I, unfortunately, don't have a thread created on Laracasts forum.

I was using Laracasts forum and Stackoverflow to look for solutions, and I posted the issue on Laravel's discord.

driesvints commented 3 years ago

Please try the laracasts forum or laravel.io forums first.

ks217 commented 3 years ago

@SynEnt I am facing some similar confusion on deciding between using routes/web.php or routes/api.php with sanctum and would like to know more about your issue/solution. Did you post this somewhere else on laracasts/laravel.io where I can follow it?

ericwang401 commented 3 years ago

@ks217 Ah, my apologies. I haven't updated this issue report and just saw your email in my inbox today! So, I found that this issue was because of my misconfiguration on Postman (not the server), and not the maintainers/developers of Sanctum.

Apparently, when I made the new request to my endpoint https://<host>/api/client/wow, I did not include the Referer: {{ host }} part in the headers, which triggered Sanctum to disallow the request (to prevent XSS, I presume).

So, what I did to fix my own error was to add the Referer header in my request. image

If you want to prevent this in the future, I recommend saving all your requests in a folder like this: image

And adding this script (you have to set your environment variables, check my reproduction for instructions): **pm.request.headers.add({key: 'Accept', value: 'application/json' })

pm.request.headers.add({key: 'referer', value: pm.environment.get('host') })

pm.sendRequest({ url: 'https://'+ pm.environment.get('host') +'/sanctum/csrf-cookie', method: 'GET' }, function (error, response, { cookies }) { if (!error) { pm.environment.set('xsrf-token', cookies.get('XSRF-TOKEN')) } })**

Here is how you can apply it to all future requests, so you don't forget them.

Step 1: image

Step 2: Click the tab in the screenshot below and paste the code. From now on, every request you save in that folder, it will automatically append the Accept and referer header to each request. image

And your question about deciding between web.php and api.php

I would choose api.php if you are creating APIs for SPA apps or REST APIs.

In fact, you can also create your own route files in the RouteServiceProvider.

You can take a look at how I am organizing my routes below: https://github.com/StratumPanel/Stratum-Panel/blob/main/app/Providers/RouteServiceProvider.php

ks217 commented 3 years ago

@SynEnt Hey, Thanks for the detailed reply and sharing your code! I had already setup Postman the same way as you mentioned. I did not face issues with that. I will see if RouteServiceProvider helps my case.

As of now, I have figured that

  1. Login routes for SPA: Using web.php works fine. Came across issues with api.php since its stateless.
  2. Login routes for mobile app: Create different login routes for handling tokens in api.php.
  3. Protected/authenticated routes: Use in api.php with "auth:sanctum" middleware. This makes sure they will work for both SPA and a mobile app.