Stateless API with JWT cookie invalidation - laravel

I have been playing for a couple of days with a stateless Laravel API with JWT based authentication, where I store the token in a cookie.
All is well, except for the cookie (in)validation...
I set my cookie like so
Cookie::queue(Cookies::NAME, $token, (int) env('COOKIE_VALIDITY'), Cookies::PATH, env('COOKIE_DOMAIN'), env('COOKIE_SECURE'), Cookies::HTTPONLY, Cookies::RAW, Cookies::SAMESITE);
$redirectUrl = $request->query('redirectTo') ?? route('home');
return redirect($redirectUrl);
This works, it does set the cookie.
However, my cookie Expires/Max-Age seems to be one hour behind, always.
2019-10-12T08:51:35.737Z (when it is actually 9:51)
The cookies gets sent correctly on all subsequent requests though.
The biggest problem however, is cookie invalidation.
My logout action looks like this
return redirect('/login')->withCookies([Cookie::forget(Cookies::NAME)]);
The action gets called to, but the cookie remains unchanged.
I also tried with the cookie()->forget() helper, this has the same result.
Any clues to what I am doing wrong here?
Ps: I do see that laravel by default adds a session cookie as well, I suppose this is normal, due to the fact that I reach the site as an anonymous user and therefor receive a session for that?
Im asking because the challange for me is to have a full stateless API that only uses server side rendered login page and then redirects back to some kind of SPA.
All help is much appreciated.

To whom it may concern, I have found the problem.
Apparently, specifying the cookie name is not sufficient.
return redirect('/login')->withCookies([Cookie::forget(Cookies::NAME, Cookies::PATH, env('COOKIE_DOMAIN'))]);
Works perfectly.

Related

Laravel passport: increase "laravel_token" cookie expire time

I have Laravel Passport implemented in my project and it is everything working well except the cookie expiration time where the tokens are being stored (that is just 1 hour).
My project consists in a backend Laravel 5.8 api (with Laravel Passport) that serves a front SPA app (Vue).
Users from my app can login successfully using a page with a Vue component that makes a POST request with the user credentials and, if the login is successfully done, users are redirected to a new URL (app home) - this redirection is a GET request that creates the "laravel_token" cookie - created by the CreateFreshApiToken middleware.
From now on, users can go everywhere inside the app and all data needed from the app components' is obtained through ajax calls (Laravel will note the presence of the cookie "laravel_token" in these ajax calls and will identify the logged in user using the JWT present in that cookie).
My problem is:
The "laravel_token" cookie that was created when user logged in was created with a lifetime of just 1 hour. Because this is a SPA, this cookie never gets updated (exchanged by a new one, with a new hour lifetime)... so, after 1 hour, when a new ajax request needs to be done to the backend Laravel server, it will receive an Unauthenticated response - that makes sense because "laravel_token" cookie is outdated.
How do you deal with this problem?
I know that i can refresh this cookie by perfoming a full refresh/reload of the page before this cookie expire but this is not a good solution in terms of user experience.
I can't make an ajax call to refresh this cookie because this is a SPA and i don't have the client_id and it's secret from client side... and also because not only this cookie is httponly but also it is encrypted by Laravel - so, i can't exchange it by a new one using JS.
Is the only solution increase the lifetime of this cookie (from 1 hour to.... 1 year, for example)? Do you see any problem with this? And where can i set this cookie expiration time? Does i need to extend the ApiTokenCookieFactory class?
I would like user to be logged in until he deliberately performs a logout request or the access_token expires (that, in my case that i am using Laravel Passport defaults, is a long-lived token of 1 year).
I would appreciate if someone could help me with this problem.
If you see something that i am not doing the correct way, i also would appreciate your comments with suggestions.
Thank you very much!
According to ApiTokenCookieFactory an expiration time of laravel_token cookie is getting from session lifetime value.
Well, just change value of SESSION_LIFETIME in .env

Sentry Cookie not attaching

I am working on Laravel 4 application and using Sentry for authentication. I need to add Keep Me Logged In functionality into my application. I have googled around and found that passing second variable to Sentry::login($user, $remember) sets up a cookie. I have done that and can verify that it is working from the browser (Chrome). But somehow whenever I try Sentry::check() after a day it returns null for cookies. Even when the cookie is present in the browser. Can anyone point out what am I doing wrong? Same happens when I attach my custom cookie to the response.
This scenario happens on my production server. Whereas it works fine on my local server.
PS: Lifetime of the cookie is set to forever (5 Years)
After working around for sometime on the issue I was finally able to resolve the issue by creating and attaching custom cookie to the response after login. And then wrote a middleware to check for that cookie. If present then login user and continue.

Get a cookie in Laravel 5 middleware

I'm trying to retrieve a cookie from a middleware in Laravel 5.3 but it seems like $request->cookie('language') is empty. I'm guessing that it is only set after the middleware runs.
I read somewhere that I should use \Cookie::queued('language'), but it's still empty.
Is my only option using the $_COOKIE variable?
When do you set this cookie?
Remember that cookies are stored in the browser, so the user needs to get the response in order for you to be able to retrieve the cookie later.
You should be able to get the cookie after the cookie is being set by a response that's successfully sent to the user. Remember also that if you use dd(), that doesn't let the cookie get created, because it skips all cookie headers from being sent to the user.
Another problem you might face for trying to get cookies from middleware is that it might not get decrypted automatically, so you'll have to do it yourself.
Example:
\Crypt::decrypt(Cookie::get('language'))
If someone encounters this problem in 2019 with Laravel 5.8:
You will need to use \Crypt::decryptString(Cookie::get('language')) or \Crypt::decrypt(Cookie::get('language'), false).
Otherwise it will try to unserialize the string and then strange things happen.

Auth::user() returns null with CORS requests

I have a site with a separate REST API on a subdomain, e.g. api.mysite.com, that I send CRUD requests to. The API sub-domain has this filter adding the appropriate headers to the response:
// Simple CORS handling
Route::filter('cors', function($route, $request, $response) {
$origin = Request::header('Origin');
$host = parse_url($origin, PHP_URL_HOST);
// Don't send response for external domains.
if (!in_array($host, Config::get('domains'))) {
App::abort();
}
$response->headers->set('Access-Control-Allow-Origin', $origin);
$response->headers->set('Access-Control-Allow-Headers', 'Accept, Accept-Encoding, Accept-Language, Content-Length, Content-Type');
$response->headers->set('Access-Control-Allow-Methods', 'DELETE, GET, PATCH, POST, PUT');
$response->headers->set('Access-Control-Allow-Credentials', 'true');
});
I'm also setting crossdomain: true and xhrFields: { withCredentials: true } on my jQuery $.ajax request. The requests manage to go through to the server, hit the appropriate routes, etc, but something is going wrong with the authentication process. Every time, Laravel acts as if the user hasn't logged in, causing requests to Auth::user() to return null. Inspecting the requests in Firebug shows that the Cookie header is sent in the request with the Laravel session id, but the server responds with SetCookie, as if trying to start a new session. I'm probably doing something dumb here, but I'm at my wits end trying to determine just what.
Update: From some debugging, I've found something interesting out. Not sure what it means, yet. To get to the page in question, the user must be logged in. Therefore, there is a laravel_session cookie in the browser when the page loads. I then send off a couple of (cross-domain) AJAX requests by interacting with the page. The first request has no cookie set at all and gets a new laravel_session cookie set from the server. The second request then includes that cookie, but the response to it sends back yet another new cookie, as if the back-end never got the memo about the first. I'm starting to wonder if this isn't something to do with cookie domains or some-such.
I finally figured it out.
First of all, the xhr and route filter were both configured correctly. The root cause of this problem was definitely a cookie issue.
The cookie domain for laravel_session was not initially set. Browsers interpret that to be a short-hand for "current domain". That is, app.mysite.com What I needed to do was explicitly set the value in Laravel's session.domain configuration to be ".mysite.com" That way, the same session cookie becomes available to app.mysite.com, api.mysite.com, and any other sub-domains of mysite.com Problem solved!
That said, there were two gotchas that I tripped over on my way to this solution:
The first was that cookies cannot be set for TLDs. I normally set up my development domain to be something like "mysite", leaving off the TLD. As far as DNS is concerned, that IS a TLD, and the cookies will fail. Once I changed my fake domain for development over to "mysite.dev", "mysite" was no longer a TLD, and the browser accepted cookies for it.
The second was that I had to remove my session cookie from my browser before I could log in to the new and different domain. I don't know why this is the case, but remember to clear your session cookie out when doing this.
Obviously, this is too much to ask your users to do. If you're putting this kind of change out into an already deployed site, you need to consider how to get your users migrated over to the cookie changes.
Since Laravel session cookies are set with an expiry time not too far into the future, one option is to simply deploy such changes when your users are not very active and accept the app appearing to be broken until all session cookies have expired. Only your currently and recently active users will be affected and the "solution" is nice and easy. But your app is broken for a while.
The other option is to change the name of the session cookie away from "laravel_session" at the same time that you change the cookie domain. This way, the new cookie sits beside the old one while the old ones expire and your app remains unbroken.

Securing an ajax request

i have a website that uses session cookies for security. it works fine and all, but any ajax requests right now are not secure. example being lets say a user is on a page. they can only get to this page if they are logged in with a session - so far so good. but now the ajax request they ask for is
ajaxpages/somepage.php?somevar=something&anothervar=something
if any other user decides to just go to that link themselves (without a session) they still get the same ajax output that was meant for logged in people.
so obviously im going to have to pass session data across when i send an ajax request. anyone have any tips for the best way of doing this? ive never done this before and would rather use trusted methods than make up my own.
The ajax requests work just like any other request to your website and should return the same session cookies as the non-ajax request. This is pointed out in this question. If you aren't getting the session cookie, perhaps something else is wrong.
Having an ajax output isn't necessarily a vulnerability. It entirely depends on what data is being transmitted. I am not sure what platform you are using, but most web application development platforms have a session variable that can maintain state between requests.
What you should have in place is way of marking the user as being logged in from the server side. I php this would look like:
if(login($user,$password)){
$_SESSION['logged_in']=true;
}
Then you can check in a header file if they are allowed to access the page:
if(!$_SESSION['logged_in']){
header("location: http://127.0.0.1/");
die();
}
(If a variable isn't set it is also false.)
There are a few things you need to keep in mind. This is a vulnerability:
if(!$_COOKIE['logged_in']){
header("location: http://127.0.0.1/");
die();
}
The user can control $_COOKIE, so they can tell you that they are logged in.
Another vulnerability:
if(!$_COOKIE['logged_in']){
header("location: http://127.0.0.1/");
}
header() doesn't kill the script. In fact it still runs, so it will still output but it won't be displayed in a browser, you can still use netcat/telnet/wireshark to see the data.
Use the same security check on the pages that handle the ajax request.
Since that is a PHP page, I don't see why you couldn't perform authentication on the PHP side. If authentication is successful, send back the data. Otherwise, send back an error message. AJAX aren't that different from any other request.
Just let ajax carry the session cookie, there is no problem with that, but you must check if the user is logged or not at the end, and you might want to add some CSRF token for your request, just in case ...
And try to validate the referrer, so you can check if the request was sent from your website, and your website only, it's not a good practice to let user open your request url for ajax in their browser ....
And if you have query in your script, to get some data from your database or else ... don't forget to sanitize the input, and escaping the output, based on what kind of data that you need, once more just in case ...

Resources