Laravel CSRF on second (third, etc.) ajax request - laravel

In a laravel application, I have a form which I submit by javascript. I added the {{ csrf_field() }} to the form and I am using the VerifyCsrfToken middleware.
The first request works fine and as expected. But if I don't refresh the page and resend the same form (for example because of form field validation errors), I get a 419 error on my request. I think its because the _token is the same in both requests and somekind of invalidated on the first request.
Is there a way, to prevent a csrf token to be invalidated on a request, so that I can reuse it as long as I need it?

If you're not using the Axios HTTP library (it's included in bootstrap.js file) you will need to manualy update the CSRF token from the received cookies of the previous request.
the Axios HTTP library provided in the resources/js/bootstrap.js file automatically sends an X-XSRF-TOKEN header using the value of the encrypted XSRF-TOKEN cookie. If you are not using this library, you will need to manually configure this behavior for your application.
--EDIT--
Since your pages are cached and static. either lose the caching or lose the CSRF
in app\Http\Kernel.php comment the line for the middleware VerifyCsrfToken
protected $middlewareGroups = [
'web' => [
\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, <--- THIS ONE
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CheckBlocked::class,
],
];

Related

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

Laravel Sanctum CSRF Cookie Request Optional

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.

Save laravel_session cookie from ajax request - Single sign on

I'm trying to implement a single sign on.
The other application makes a http post request to my app passing some parameters, I make a request back and get an email address for the current user. (Yes, I've set the necessary CORS header in my .htaccess)
I'm now creating a user instance with that email and try to authenticate the user.
Auth::login($user, true);
return response("OK", 200);
This works fine (if the user was found).
I now expect that with Auth::login an authenticated session gets created and a session cookie is saved. As soon as the user opens my app, this session is found, no further login is necessary and the dashboard opens.
However no cookie is saved at all.
I then tried to save the cookie manually using
response("OK", 200)
->cookie("laravel_session", Session::getId(), 60);
I also tried
response("OK", 200)
->cookie("laravel_session", encrypt(Session::getId()), 60);
And
Cookie::queue(Cookie::make("laravel_session", Session::getId(), 60));
response("OK", 200);
In every case the cookie was set but when opening my app the login screen was shown. So my guess is, that there must by anything I'm missing or the laravel_session cookie contains some other data.
So my question is:
Using an ajax post request, how do I properly authenticate a user and save that as a cookie, so he will be logged in when he opens up my app?
UPDATE
I've added some middleware in my kernel.php. Now the session cookie gets set automatically. However it doesn't seem to get encrypted. The "laravel_session" cookie value from the ajax request now looks like this: lYLxSwyuhfHToruJhc0r2zWNLkj9ONTlBM3QjkAo. However the cookie beeing set when using the usual "web" route looks like this: eyJpdiI6InV5bGRQNFJ4c01TYjZwT0I0amxzS1E9PSIsInZhbHVlIjoiZFI2WWpVWGxmTldDcVJvVlwvbVJicXBxM0pjRkVRUlBRKzZWb1BkbzliZHBVdTlmUEV4UzZkaFVMbmlRTHNYczFOZm5HSWkwRXhjb3BJRGI1NGRyM2tnPT0iLCJtYWMiOiJjMjAwMWIyMGIxYmQwYzkxMGQyNGJhMDZmZDJiNThjNGZhMTUyZWVjZDlkNjg5ZWVjYjY2MGE1ZTlmZDAxOGNmIn0%3D
Here is my ['api'] value in the kernel.php protected $middlewareGroups
'api' => [
'throttle:60,1',
'bindings',
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class
],
However, when setting a session cookie with a different name directly in my response, the value seems to be encrypted now:
return response("OK", 200)
->cookie("my_session", Session::getId(), $minutes);
Thanks a lot for help.
I've found a solution. I need the StartSession::class and the EncryptCookies::class definition in my Kernel.php file AND EncryptCookies:class must be the first element in the list. It didn't worked for me when directly placing it right before StartSession::class
Here is my working "api" definition from Kernel.php
'api' => [
\App\Http\Middleware\EncryptCookies::class,
'throttle:60,1',
'bindings',
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
],
Now the session cookie is set and encrypted. Yeah!

Laravel enable VerifyCsrfToken for specific routes

Running Laravel 5.3 I have certain API routes that my application calls for guests on the site.
I do not want these to be completely public and directly accessible so I'm think that I need to add "VerifyCsrfToken" middleware to these routes only.
I believe that would at least confirm that the request is coming from my application.
Any guidance on this? Thanks
UPDATE:
After adding the csrf middleware to the route as suggested by #motie and then also adding the "StartSession" middleware the route seemed to work - but it did not actually work according to what I had hoped - Was hoping for it to compare tokens and the fail if mismatch - but that was not the case.
The VerifyCsrfToken has the following in its checks:
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->shouldPassThrough($request) ||
$this->tokensMatch($request)
)
It requires any one of those conditions to pass - which is a bit strange as the "isReading" simply checks if the request uses "GET" method present so it always passes...
Also calling the StartSession middleware seems to refresh the csrf token for the API route which means the web token and api call token are not the same. I'm think it might just be easier to add this route to the "Web" routes or add the "web" middleware to it...
To enable csrf for some api routes, define an alias for it in ```app/Http/Kernel.php::
tected $routeMiddleware = [
...
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class,
'session' => \Illuminate\Session\Middleware\StartSession::class,
...
]
then you will be able to add it to the routes you want in api.php.
Route::get('secured-api-route', ['middleware' => 'csrf']);
Edit:
Using the csrf middleware requires activating the session middleware. Hence, it will be better to create a new middleware group than registering separate middlewares.
'api.csrf' => [
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
]

How to use Postman for Laravel $_POST request

How can I try sending a post request to a Laravel app with Postman?
Normally Laravel has a csrf_token that we have to pass with a POST/PUT request. How can I get and send this value in Postman? Is it even possible without turning off the CSRF protection?
Edit:
Ah wait, I misread the question. You want to do it without turning off the CSRF protection? Like Bharat Geleda said: You can make a route that returns only the token and manually copy it in a _token field in postman.
But I would recommend excluding your api calls from the CSRF protection like below, and addin some sort of API authentication later.
Which version of laravel are you running?
Laravel 5.2 and up:
Since 5.2 the CSRF token is only required on routes with web middleware. So put your api routes outside the group with web middleware.
See the "The Default Routes File" heading in the documentation for more info.
Laravel 5.1 and 5.2:
You can exclude routes which should not have CSRF protection in the VerifyCsrfToken middleware like this:
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'api/*',
];
}
See the "Excluding URIs From CSRF Protection" heading documentation for more info.
If you store your sessions in Cookies, you can grab the Cookie from an auth request in Developer Tools.
Copy and paste that Cookie in the Header of your POSTMAN or Paw requests.
This approach allows you to limit your API testing to your current session.
1.You can create a new route to show the csrf token using your controller with help of the function below.
(Use a Get request on the route)
public function showToken {
echo csrf_token();
}
2.Select the Body tab on postman and then choose x-www-form-urlencoded.
3.Copy the token and paste in postman as the value of the key named _token.
4.Execute your post request on your URL/Endpoint
In the headers, add the cookies, before making request, the XSRF-TOKEN cookie and the app cookie. I.e yourappname_session
In laravel, 5.3. Go to app/Http/Kernel.php find middlewareGroups then comment VerifyCsrfToken. Because it executes all middleware before service your request.
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
***// \App\Http\Middleware\VerifyCsrfToken::class,***
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];

Resources