Save laravel_session cookie from ajax request - Single sign on - laravel

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!

Related

Should we make a request to /sanctum/csrf-cookie first, before registration that logs in a user after a successful registration?

My SPA is in the same repository as my laravel application and the documentation states that when using sanctum, your SPA's "login" page should first make a request to the /sanctum/csrf-cookie endpoint to initialize CSRF protection for the application.
Link: https://laravel.com/docs/9.x/sanctum#spa-authenticating
In my case, When I register a user successfully I'm not redirecting them to the login page to log in but rather to their dashboard. So, in my understanding going by the point above, I think I should first make a request to the /sanctum/csrf-cookie endpoint as well before making a post request to the register api so that we have a logged-in user that is protected from CSRF attacks but I'm not that sure if I'm interpreting the text correctly.
my method
public function register(Request $request){
$fields = $request->validate([
'name' => 'required',
'email' => 'required|email|unique:users,email',
'password' => 'required|confirmed',
]);
$fields['password'] = bcrypt($fields['password']);
$user = User::create($fields);
auth()->login($user);
}
I investigated this issue further and found that the /sanctum/csrf-cookie endpoint actually only returns a 204 empty content. You can check here:
https://github.com/laravel/sanctum/blob/5a602d520474e103174900301d7b791e6d7cd953/src/Http/Controllers/CsrfCookieController.php#L12
return new JsonResponse(null, 204);
And the comment in the file says:
Return an empty response simply to trigger the storage of the CSRF cookie in the browser.
Actually you can call any GET API endpoint from your SPA and it will return the CSRF cookie. Basically you just need to have called a GET endpoint once before calling any POST endpoint, including the login endpoint, which should be POST.
The reason for this is that sanctum by default returns a CSRF cookie when you call a GET endpoint for SPAs (using same host or same sub-host).
So for most use cases out there you might not need to call the /sanctum/csrf-cookie endpoint before login, because you might have already called a GET endpoint before that. However if the login, or any other POST endpoint is the first one you are calling, you first need to call the above GET endpoint just to trigger the storage of the CSRF cookie in the browser.
The docs are not so clear on this I am trying to submit a PR to clear this up.

How to use Laravel and Nuxtjs with Authentication, including login, logout, and password resets?

I can't find any resource to help with this issue, there are some repos that provide some type of base, but not many of them actually work.
Goals: Run Laravel has the backend API, run NuxtJS as the frontend SPA, either 2 separate locations or combined into one.
Needs to have proper authentication between both systems for logging in users. Laravel Sanctum looks to solve some of the SPA issues, but its hard to find proper documentation that actually shows a fully setup example.
Tested this idea https://github.com/zondycz/laravel-nuxt-sanctum But has failed, doesnt work with npm, must use yarn, however, login errors, doesnt work out of box. Repo needs work.
This tutorial Secure authentication in Nuxt SPA with Laravel as back-end was very indepth, and looked promising, however, refresh tokens don't seem to work with the SPA side since the author developed it on static nuxt. Though I feel like it could be modified to work, I havent found the solution yet.
This template, Laranuxt looked very promising, though I've yet to try it, im not sure if they are regularly updating it at this point, which was previously built by Laravel Nuxt JS (abandoned project)
I was able to run the #2, while refresh tokens dont exactly work, I can still authenticate the user, but now the other issue is password resets, which I'm unable to properly setup through the nuxt form.
Has anyone found resources or solved this issue with communication between these frameworks? or am I going done a rabbit hole that seems to have no end in sight?
I guess another way could be saying, can you do a fully restful authentication system?
Hopefully this isn't too broad of a topic, looking for some guidance on this issue as its hard to find proper tutorials or documentation without writing too much core code myself.
It seems many people struggle to implement Sanctum for SPA authentication when splitting the front and back across separate domains, and the problem is usually CORS related. The Sanctum documentation is great, but assumes a knowledge of CORS (or assumes requests will be same-origin). I'll break down the setup as I see it, providing a little extra support where I feel the docs fall short. A long answer, but towards the end I will address your question which seems to focus specifically on authentication.
Taken from Sanctum documentation:
First, you should configure which domains your SPA will be making requests from.
Assuming your front-end app lives at https://www.my-awesome-app.io, what is the domain? What about http://localhost:3000? Domains map to IP addresses, not protocols or port numbers. So the domains in the given examples would be www.my-awesome-app.io and localhost. With that in mind, all you need to do at this stage is go to the sanctum.php file in your config directory and set the value of the 'stateful' key to match the domain your Laravel API will receive requests from. Although domain names by definition do not include port numbers, the Sanctum docs make it very clear this is also required if you're accessing via a URL that requires a specific port.
/config/sanctum.php
...
'stateful' => [
'localhost:3000',
],
or
'stateful' => [
'my-awesome-app.io',
],
.env files are useful here.
If you are having trouble authenticating with your application from an SPA that executes on a separate subdomain, you have likely misconfigured your CORS (Cross-Origin Resource Sharing) or session cookie settings.
Indeed. So what does a correct setup look like? Assuming a recent Laravel version using the fruitcake/laravel-cors package, you will have a cors.php file in your /config folder. The default looks like:
Default
/config/cors.php
...
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
We have some work to do here. First, the paths. At the moment, our Laravel API is set up to allow requests from any external origin only if they are trying to access the /api/ routes*. This can lead to trouble early on when, as the Sanctum docs require, we try to access a csrf cookie from the path /sanctum/csrf-cookie. Requests to this path are not explicitly permitted in our cors.php file, so they will fail. To fix, we could do this:
'paths' => [
'api/*',
'sanctum/csrf-cookie'
]
and now requests to /sanctum/csrf-cookie will be permitted. As a sidenote, I find it personally very useful to change the prefix from sanctum to api, that way I can set a single base url for my http client (usually axios).
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'http://localhost:3000/api';
To change the path, you can change the following in the /config/sanctum.php file:
'prefix' => 'api',
Now GET requests to /api/csrf-cookie will return the cookie, instead of /sanctum/csrf-cookie.
Next, the allowed-origins. By default, it is set to *, which means "any origin". An origin is the protocol, domain and port number of the app sending a request to your Laravel API. So going back to our earlier examples, their origins would be http://localhost:3000 and https://www.my-awesome-app.io. These are the exact values you should use to allow requests from your front-end app:
'allowed_origins' => ['http://localhost:3000'],
I would recommend moving this to the .env file, and having a separate origin for local and production.
/config/cors.php
...
'allowed_origins' => [env('ALLOWED_ORIGINS')],
/.env
...
ALLOWED_ORIGINS=http://localhost:3000
The documentation does mention the last part of our cors config, which is that
'supports_credentials' => false,
Must be changed to:
'supports_credentials' => true,
Our /config/cors.php file now looks like:
Modified
/config/cors.php
...
'paths' => [
'api/*',
'sanctum/csrf-cookie'
],
'allowed_methods' => ['*'],
'allowed_origins' => [env('ALLOWED_ORIGINS')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
Bonus info, Chrome will not allow a credentialed request to a server that returns the header
Access-Control-Allow-Origin: *
Google Chrome: A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true
So you should make sure you set a specific origin in your cors config!
Finally, you should ensure your application's session cookie domain configuration supports any subdomain of your root domain. You may do this by prefixing the domain with a leading . within your session configuration file:
This isn't complicated, but seems it can catch people out so thought I'd mention it. Given our examples so far, we'd make the following change to our config/session.php file:
'domain' => '.my-awesome-app.io',
Locally, localhost alone is fine:
'domain' => 'localhost',
Assuming you've followed the rest of the instructions in the Sanctum documentation (setting axios.defaults.withCredentials = true;, adding the middleware etc) your backend configuration is now complete.
Front end and authentication.
I love Sanctum and I'm very grateful for the creators; so I say this with respect; the documentation lacks a little depth at this point. Grabbing the csrf-token is very straight forward, and then...
Once CSRF protection has been initialized, you should make a POST request to the typical Laravel /login route. This /login route may be provided by the laravel/jetstream authentication scaffolding package.
If the login request is successful, you will be authenticated and subsequent requests to your API routes will automatically be authenticated via the session cookie that the Laravel backend issued to your client.
It seems they've updated the docs!
As I write this, I've checked the latest docs and it's now highlighted the fact that you are free to write your own login endpoint. This was always the case, but might have escaped a few people, perhaps given the instructions above ("you should make a POST request to the typical Laravel /login route.") It's also perhaps not clear that you can override default Laravel methods to prevent unwanted side-effects of the default auth setup, like redirecting to the /home page etc.
Writing your own login controller is simple, and I prefer to do so for Sanctum. Here's one you can use:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
public function login(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
'password' => 'required'
]);
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
return response()->json(Auth::user(), 200);
}
throw ValidationException::withMessages([
'email' => 'The provided credentails are incorect.'
]);
}
}
Feel free to modify this to suit your needs.
How you manage your state (making sure your app remembers you are logged in, for example) on the front-end is also up to you. There are lots of options, however if we're using Sanctum I think we should focus on a simple cookie-based approach. Here's one I use:
Login to your app. An auth session is established and your browser saves the cookies provided by your Laravel API.
Have your login script return the authenticated user (the one provided above does just that). Save the details of that user to your app state (eg. Vuex).
Check your state contains the user any time you need to secure an action against unauthorised users. Redirect to the login page is the auth check fails.
Here's the above in Nuxt.js form using middleware.
/middleware/auth-check.js
export default async function ({ store, redirect }) {
// Check if the user is not already in the store.
if (store.state.user === null) {
// Call your Laravel API to get the currently authenticated user.
// It doesn't matter if the store has been wiped out due to a page
// refresh- the browser still has the cookies, which will be sent
// along with this request.
try {
let rsp = await user.getAuthenticatedUser()
// If we get the user from the Laravel API, push it back in to
// the store and carry on to the page.
store.commit('SET_AUTH_USER', rsp.data)
} catch (e) {
// If our API doesn't return the user for any reason, redirect to
// the login page.
return redirect('/login')
}
}
// If not, carry on to the page.
}
/pages/admin.vue
export default {
middleware: auth-check
}
The code above is for example purposes, but it's generally what I use for Vue/Nuxt and Sanctum.
Hope this helps, happy to elaborate further if anyone can benefit.

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

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,
],
];

Laravel sessions not saving when called from Postman or Guzzle

I'm experiencing a problem with sessions in Laravel. My project consists about two projects, one an API and another a WebApp. Both with Laravel 5.5.
The problem is that I want to save a session in my API project but it isn't saved. I save the session like this in api.php:
Route::get('test', function () {
session(['data' => "data"]);
session()->save();
});
If I visit: http://mydomain.dev/test through Firefox, I can see the session in the Laravel DebugBar because it has been saved:
If I make a request with Postman to that URL, session doesn't appear in the Laravel DebugBar! It isn't saved.
After some research, I found this question and people say to include in Kernel.php these two lines:
protected $middleware = [
//...
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
I have already added those two lines and the problem persists.
The same happens with Guzzle. From my WebApp, I make a GET call to my API. In the method called in the API, I save a session, and when I retrieve the session in another method of the API, I get null because session hasn't been saved!
My suspicion is that Postman and Guzzle problems with sessions are related, and that there's something I'm missing.
My config\session.php files are as default. I know I have as alternative to save sessions in database, but I would prefer to keep it as default, but if I don't have any alternative, I will change my SESSION_DRIVER option from session.php from file to database.
APIs are and should be sessionless/stateless.
They can not handle it.
That's why token based authentications are used.
In order to make you sure about it. Write a route in web.php file and hit it from postman or browser then you will see a session info if any.

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