Laravel Sanctum CSRF Cookie Request Optional - laravel

I am trying to use Laravel Sanctum for my SPA. There are some basic home pages from web.php routes but other axios API interactions with the SPA are in api.php routes guarded by auth:sanctum
From the official documentation (https://laravel.com/docs/7.x/sanctum#spa-authenticating), it says we have to send a request to /sanctum/csrf-cookie to initialize CSRF protection prior login. However, I noticed that even without login, Laravel by default already initialized XSRF-TOKEN and <app_name>_session cookies to my browser. I do not need to initialize it via /sanctum/csrf-cookie and my subsequent API request in the logged-in SPA still works. Later I checked https://laravel.com/docs/7.x/csrf#csrf-x-xsrf-token and it says it is the default behavior that Laravel will include the CSRF token in each response.
My question is, is it true that /sanctum/csrf-cookie initialization is optional and it is safe for axios to use the default CSRF token return by Laravel? Or am I doing something wrong which exposes my SPA to CSRF attack?

Your main SPA home page is probably provided by a route that is defined in your web.php route file as you mentionned. In App/Http/Kernel.php, check in your middleware groups if there is VerifyCsrfToken::class defined as a middleware for web :
protected $middlewareGroups = [
'web' => [
...
StartSession::class,
...
VerifyCsrfToken::class,
...
]
]
This middleware is responsible for creating header response like : set-cookie XSRF-TOKEN=kgXZBZ4AccC0H17KEMw.... when you request any route available in web.php (if the cookie yet doesn't exist obviously), that will indeed initialize a XSRF-TOKEN cookie.
Therefore, you don't need to request route /sanctum/csrf-cookie when you already use this VerifyCsrfToken middleware.
However, if you are doing full SPA totally separated from your Laravel backend and deliver a html page differently, you won't have this XSRF-TOKEN cookie generated by default. Thus, as mentionned in Sanctum documentation, you need to request /sanctum/csrf-cookie to generate cookie before going further.

Related

How Laravel middleware auth:api detect token from cookie?

I just trying to makes my auth flow more secure using a cookie on Laravel 5.7
Here my code
/**
* auth logic
*/
return response()->json(["status" => "logged in"], 200)->cookie('token', $token, $lifetime);
Then the cookie will be saved on the browser and will be used on every request.
On header with Axios
"cookie":"token={token}"
And I validate the auth using default middleware
Route::group(['middleware' => ['auth:api']])
But the auth:api did not recognize it, I can make custom middleware by manually detect the cookie, but I can't use the auth()->user() function on it.
Is there any solution for this?
From your sample code I believe your app is built on a stateless architecture where you have your JavaScript client and laravel api.
Now I am a bit confused as to why you do not want the client storing the token, if you just want to escape cross site scripting vulnerability (XSS) then you have to prepare to deal with cross site request forgery (CSRF) if you store the token in the browsers cookie.
Regarding the middleware not being able to find the token, by default the middleware is configured to lookup tokens in the request header (specifically the Authorization header) so if you decide to store it in the cookie, you have to find a way to change the token lookup in the api middleware which unfortunately I have not done before in laravel.
APIs don't generally store and send cookies. Therefore the api token authentication guard will not look for the token in a cookie. There are multiple options you can send it as though the easiest one in axios:
{
headers: {
Authorization: `Bearer ${token}`
}
}

Sanctum SPA Authentication - web.php vs api.php

I am using Sanctum for SPA authentication. In several examples I have seen, people are creating auth routes (login, logout, register) in their web.php routes file as opposed to the api.php routes file. Is there a reason for this? In the documentation I do see a mention here...
You may be wondering why we suggest that you authenticate the routes
within your application's routes/web.php file using the sanctum guard.
Remember, Sanctum will first attempt to authenticate incoming requests
using Laravel's typical session authentication cookie. If that cookie
is not present then Sanctum will attempt to authenticate the request
using a token in the request's Authorization header. In addition,
authenticating all requests using Sanctum ensures that we may always
call the tokenCan method on the currently authenticated user instance
...but that is for API Token Authentication and not directly under SPA Authentication.
Is there any reason my auth routes would be better handled in web.php?
Well, in a typical Laravel application, your API routes are stateless and do not persist a session; specifically they do not have the start session middleware.
As such, cookie based authentication will not work if you put these routes in your API file.
Having these routes in your web file allows these specific routes to be wrapped in a session, allowing cookie based authentication and then falls back to using the stateless Authorization header if required.
I forget the exact words, but Taylor is quite a fan of SPAs using cookie based authentication when they're the same domain over API tokens.
But this should explain the reasoning. You are, of course, welcome to change this if you like.

Laravel Sanctum CSRF returns 419 for unprotected routes

I have a SPA app where I try to implement Sanctum's CSRF protection.
From docs:
To authenticate your SPA, your SPA's "login" page should first make a request to the /sanctum/csrf-cookie endpoint to initialize CSRF protection for the application
Right now I request CSRF token before I login
axios.get('/sanctum/csrf-cookie').then(response => {
// Login...
});
Should I request CSRF token before doing ANY post request in my application?
If yes, I basically need to request a CSRF token before routes like POST api/password_reset, POST api/tracking, POST api/register etc.
Or is there any way to tell Laravel Sanctum to only return 419 CSRF token mismatch errors for protected routes, ie. routes with auth:sanctum middleware?
EDIT:
Just wanted to make it clear that I don't have an issue with CSRF implementation in general. It works great after I have requested the CSRF token. Axios will add the token in all subsequent requests. My question is really about when to do the first request to CSRF token.
I have searched for something similar and I stumbled upon this; Laravel: How to Avoid TokenMismatchException CSRF Token on certain routes using a method
You can therefore kindly exclude the routes from being checked for CSRF token by adding the route path in $except array in VerifyCsrfToken class inside the app/Http/Middleware/VerifyCsrfToken.php like shown below;
protected $except = [
'/api/password_reset',
'/api/tracking',
'/api/register',
];
This can be seen also in Laravel Official Documentation Excluding URIs From CSRF Protection

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!

Laravel 5.3 and VueJS 2 Authenticating

Im using Laravels default auth to lock down some paths in the routes/api.php file.
Route::get('/projects', 'ProjectController#index')->middleware('auth:api');
I log the user in via a PHP form (not via a http request via Vue).
This creates the session and when I submit a HTTP request via Vue I can see that the header includes the Cookie and X-CSRF-Token however I keep getting a 401 {"error":"Unauthenticated."}
In my config/auth I have api driver set as 'token' (have tried changing this to 'session' but that did work :/)
From my understanding and what I have read online I should be able to use the default Laravel auth functionality to accomplish API calls from Vue HTTP requests.
this.$http.get('/api/projects')
.then(response => {
this.projects = response.body;
})
.catch (err => {
console.log(err);
});
I've read about methods of authenticating by generating an JWT token and storing that in local storage when the user logs in. Is this the method I should use or should I be able to accomplish it with the default Laravel Auth middleware?
Hope my questions make sense, any help/advice would be appreciated.
The auth:api middleware doesn't use cookies, it uses api_token param, which can be passed via get or Bearer <token> header. Just use web middleware.
I suppose you need to access the same route in two ways - for API users and for browser users. So why don't you create two routes for one action?
// api group with /api prefix
Route::get('/projects', 'ProjectController#index')->middleware('auth:api');
// web group
Route::get('/projects', 'ProjectController#index')->middleware('web');

Resources