SPA Authentication Issues with Sanctum and Postman - laravel

I'm currently trying to test an SPA using Laravel 8.19.0 and Postman 7.36.1 but I keep getting an "Unauthenticated" response from a route that's guarded by "auth:sanctum", even though I have logged in correctly.
As far as I can understand, I've followed the documentation fully at https://laravel.com/docs/8.x/sanctum
in order to set Sanctum up to be used for SPA so I've done the following:
Installed Sanctum.
Published the Sanctum config.
Performed a migration.
Included the EnsureFrontendRequestsAreStateful middleware and 'EnsureFrontendRequestsAreStateful::class' to the Http Kernal.
Added my local domains (same top-level domain but 1 with the "test" sub domain and another with "api") to the "stateful domains" option in the Sanctum config file.
Set the "supports_credentials" option in the cors config to "true".
Set my top level domain, prefixed with a "." for the "domain" option in the session config.
Then, I've set Postman up using the guide at https://blog.codecourse.com/laravel-sanctum-airlock-with-postman/
so I've written a script to get the CSRF token from "/sanctum/csrf-cookie" then used said token as the value for the "X-XSRF-TOKEN" in the request header and I can succesfully log in. however, when I try to access a route afterwards that's guarded by the "auth:sanctum" guard, even with the referrer and 'X-XSRF-TOKEN' being set up in the request header I cannot access the route.
After debugging, I can see that $this->auth->guard($guard)->check() is returning false in the authenticate($request, array $guards) method where $guard = "sanctum" in \vendor\laravel\framework\src\Illuminate\Auth\Middleware\Authenticate.php on line 63 because $this->user() is null for the Illuminate\Auth\RequestGuard instance.
Any help or even ideas on things to check would be greatly appreciated as I'm unsure on what to do from here, short of spending a day digging deeper into the request guard object and its instantiation!
Thanks.

The issue a lot folk are seeing when using Postman with Sanctum SPA authentication is that you simply need to add an additional header to your requests, This can be "Referrer" or "Origin" and the value must match the domains set in the sanctum.php config file. e.g. localhost or mysite.test etc.
vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStatefull.php in the fromFrontEnd() method is where you can see this requirement. Laravel V8.x and I believe also in Laravel V7.x

Issue has since been resolved and was caused by Postman only saving the "XSRF-TOKEN" and "laravel_session" cookies to the "test" subdomain after logging in (the login URL used this sub domain) and thus not passing them to the "api" subdomain when trying to access the route which was protected by "auth:sanctum". By adding the same cookies to the "api" subdomain via the "Manage Cookies" menu in Postman, the route can now be accessed as intended.

Related

Can we remove using cookie from laravel sanctrum?

Is there way to remove cookie based feature from laravel sanctum and only use Authorization Bearer token way.
As, by default it sets and check through cookie and this feature don't work when API is deployed with different server then front-end.
Yes, it is possible.
\Laravel\Sanctum\Guard goes through config sanctum.guard, default value is web, so you can set this config in your config/sanctum.php file to whatever suits your need.
Eventually, you can change your web middleware group, if your SPA is the only GUI client.
If any of mentioned options is viable or smart is another question, to which I'd reply: "nope, configure your app properly to send and accept security headers and/or cookies with cors settings fitting your needs."

How to protect laravel api route from outside access but allow unrestricted access if request comes from frontend?

I'm building the backend with laravel and then using Vue as front-end. Data is accessed over api calls using axios. Now I have this relatively simple task but I can't seem to find the proper solution. I want one of the routes to be easily consumable by Vue compoenents without the need to log in, however I don't want that route to be publicly available for anyone to use.
Things I have tried:
Using passport to protect my routes and then use passport's CreateFreshApiToken middleware. Protection works fine, unauthorized users are not able to access the routes, however I don't get laravel_token in my cookies and therefore I can't get access to that route if I'm not logged in.
Use passport's client credentials grant access. Works fine and the way I want it to work but doesn't really make sense because if I hardcode the client_secret - anyone can access it and then use it to access protected routes. If I make a proxy-like solution, to call a controller method, which would issue a valid token and thus not exposing client_secret to front-end but then anyone could just call that route which issues the token and it would be pointless once again.
Apparently the answer is pretty simple and I was overcomplicating things. I don't know if this is the right/elegant way to do this but basically. If you don't need your api to be accessible from other applications (which I didn't) we can just put routes in web.php instead of api.php. This will ensure that web middleware is used and so it will use the basic csrf token validation, which is totally sufficient for protection against outside requests. You can also leave the route in api.php and just use web middleware on that route. The outcome is exactly what I needed - application is getting data over a route without any need to login AND that route is not available over postman or anything else.

What SESSION_DOMAIN should I use if I'm using Laravel Sail?

I want to use Nuxt.js for my frontend and laravel sanctum as my backend authentication provider. How should I set the SESSION_DOMAIN key in the .env file in my laravel project.
Also should I edit anything in the server object part in the nuxt.config.js file to make this work?
When you use Sanctum with SPA, such as Nuxt, you've the option to use either API or cookies/sessions. If your application is a first-party application on same top level domain, Laravel recommends to use cookie based approach so you can take advantage of CSRF protection. Axios and Angular Http libraries handles CSRF out of the box, so you don't have to worry too much about handling the requests headers [1].
In your case, I assume your application is first party and is on same top level domain. So your SESSION_DOMAIN value would be for example .domain.com. Also you'll need to set SANCTUM_STATEFUL_DOMAINS=domain.com as well. Usually your SESSION_DOMAIN will have just the main domain your application uses on, while SANCTUM_STATEFUL_DOMAINS will have all the subdomains (if any), that your frontend uses.
To work with Sanctum, we should be familiar with a few things first. We must use our SPA and API backend on the same domain, like frontend on domain.com and API on api.domain.com. We can not set frontend on domain.com and backend (API) on another-domain.com. The client must be able to include cookies with each request being sent to the backend.
session domain is the front-end domain name without protocol and port.
When you are working on local you must set it to localhost and when you are working on server you must set the domain name.
please follow this example of nuxt-laravel-sanctum-auth

AJAX request to Laravel backend returns 419 CSRF token mismatch

I'm creating a SPA using NextJS and I have a Laravel backend for my API. To authenticate my SPA I'm using laravel sanctum.
My API is on api.domain.com and my app is on domain.com
I've set these environment variables which are relevant to this issue:
SESSION_DRIVER=cookie
SESSION_DOMAIN=.domain.com
SANCTUM_STATEFUL_DOMAINS="domain.com"
When I log in I make a request to /sanctum/csrf-cookie to get my CSRF cookie, and I can see in my following requests I am sending the X-XSRF-TOKEN header with the value from the cookie.
I'm wondering if anyone else has had a similar issue with CSRF mismatches when using sanctum on different subdomains?
OK what ended up fixing my issue is changing the name of my session cookie to something without an underscore, very weird!

Chrome not allowing Set-Cookie even from the same (sub)domain

Here is my configuration:
SERVER
Laravel 8 application that uses Sanctum to provide access to REST API. It is configured to run on http://b8/
CLIENT
A separate Vue CLI 3.0 SPA that connects to the API using session-based cookie authentication method. I have configured Vue CLI Serve to run it at http://app.b8/.
PROBLEM
Chrome does not allow Set-Cookie operation even when both server and client are on the same domain. After going through several posts and articles, I have successfully run it in Postman, but the real Vue application that runs in the browser can't set Sanctum cookie and thus cannot login.
Here is my configuration:
.env
SESSION_DRIVER=cookie
SESSION_DOMAIN=.b8
SANCTUM_STATEFUL_DOMAINS=localhost,localhost:8080,127.0.0.1,127.0.0.1:8000,::1,b8,app.b8,app.b8:8080
vue.config.js
...
devServer: {
proxy: "http://b8/api/v1",
host: "app.b8"
},
Login function in Vue application
async login(context, credentials) {
axios.get('http://b8/sanctum/csrf-cookie').then(response => {
axios.post('http://b8/login', credentials).then(response2 => {
//successfully logged in.
});
});
},
Error msg
The outer call for /sanctum/csrf-cookie route returns successfully and brings Sanctum cookie. However, Chrome thinks that the cookie is invalid for the current domain and therefore doesn't set it. Here is the Dev Tools pane's Cookies tab after the Sanctum token call returns (tooltip showing Chrome's complaint):
Since the cookie is not set, the following login call fails with a 419 error.
What am I missing here?
Finally got it working. For any future reader, here are the things:
The first mistake that I was doing was to use a single word host name. It looks like Chrome (or Sanctum, not sure) wants you to have at least one period in the host name. So I changed it from b8 to b8.cod.
You should keep same_site attribute set to lax. none is a rather dangerous value. Chrome now shows none as a potential issue.
SESSION_DOMAIN should be set to .b8.cod with a leading period to support front-end subdomain, as correctly suggested by the documentation.
All other things should be done as suggested in the question. axios must use withCredentials set to true.
I've recently set up the exact same thing (Laravel 8 backend with Sanctum + separate Vue Frontend) and it's working.
Your server side setup looks good. Please double-check that the frontend's IP is actually included in your SANCTUM_STATEFUL_DOMAINS - that caused the 419 error for me sometimes.
On the Vue side, make sure to that you enable the withCredentials in your axios instance, e.g.
const baseDomain = "http://localhost"
const baseURL = `${baseDomain}/api`
export default axios.create({
baseURL,
withCredentials: true,
})
If the provided suggestion does not help, I can further extend my answer by showing you more of my working backend code.

Resources