Closed a8c71 closed 2 years ago
Hi there, I have the exact same question.
Maybe I don't understand the way Passport should work, but it seems that the reason we want to use Passport is because our entire Authentication System is Stateless. So we shouldn't be redirected to a login route on our API server to access Passport JSON API.
We should be able to have an App responsible for asking our login credentials, asking Passport for an access_token, then use this token to manage our clients. Right?
Would love to hear back on the subject.
Hello, I had the same issue guys, I spent 3 hours on that... All you have to do is to change the way web auth works. And in this case you need to:
'guards' => [
'web' => [
'driver' => 'passport',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
in your config/auth.php
Or the other way is to modify Laravel Passport Routes manually and remove: Passport::routes(); from AuthServiceProvider.php.
As a best practice I think is better to not use Passport::routes() if you dont need all of that options (grants.. ). In my case I use only a small part of laravel passport, and I created custom routes for Laravel Passport Controllers.
If you change the web driver to passport then the api authentication will work, but frontend authentication will break.
Heya, thanks for sending this in. See my answer here for more info: https://github.com/laravel/passport/issues/839#issuecomment-429371578
I'll mark this as a feature request.
Hey everyone. We're currently thinking of making some serious changes to the internals of Passport to make this happen. Basically all of the routes will become stateless and protected under the api
and api:auth
middlewares. This would mean that all of the functionality for setting cookies and checking CSRF could go away. This would also mean though that the CreateFreshApiToken
middleware would need to be reimplemented as something like the way the crsf_token
helper works.
Before we do this though I want to gather some opinions on this. What are your thoughts on this? Do you see any complications? Too big of an upgrade path?
Ping @sephster
@driesvints I think it's the best way to make the tool more independent. But in my case I will not have to change anything, my app already is stateless
Basically all of the routes will become stateless and protected under the api and api:auth middlewares.
@driesvints, could we make the middleware configurable? Some users have an existing server rendered app and want to add Passport for their api, other uses have a full SPA and want to add passport. This would make upgrading easier too.
Since the middleware is hardcoded in Laravel\Passport\RouteRegistrar
it isn't really easy to configure that at the moment.
This would mean that all of the functionality for setting cookies and checking CSRF could go away.
Would we switch to using a real OAuth client and access tokens instead? That might help solve #909 too. Or is the plan to keep creating a 'transient' JWT and passing it as a header instead of as a cookie?
The benefit of using a cookie for your own app is you can set HTTP Only which makes it difficult to steal the JWT with a XSS attack. If we switch to using a bearer token it will be accessible to javascript.
I have to admit I don't have the time to look at this atm. Currently trying to focus on Cashier v10, sorry. I'll try to take a look at Passport after if I get to it. In the meantime you're always welcome to send a PR so Taylor can have a look.
Any progress on this? I am in a situation where my SPA which is a standalone vue project, is isolated from the backend. I am unable to create clients because I can't provide the csrf_token
from the SPA. At this moment I have to drop the entire passport package from my project and have to build my own auth mechanism. Please do something about it.
Hi everyone 👋
I'm thinking of working on a PR for this. I'm making a SPA course so I've been digging into OAuth2 and Laravel Passport quite heavily recently and I was surprised to find out that Laravel Passport was basically only designed for server-rendered applications.
I also think like @matt-allan that the switch from stateful to stateless should be optional as it is an official Laravel plugin and it should work with "typical" Laravel applications.
One thing I love about Laravel Passport is how it supports both cookies and Bearer tokens out of the box and I think we should make sure to keep this flexibility for stateless APIs. I would also like to add that even httpOnly secure cookies are vulnerable to XSS.
if ($request->bearerToken()) {
return $this->authenticateViaBearerToken($request);
} elseif ($request->cookie(Passport::cookie())) {
return $this->authenticateViaCookie($request);
}
Aside from updating the middleware group, here are a few points that I foresee for this PR.
SessionGuard
is available.passport::authorize
page is not enough anymore since the user also needs to log in before reaching that page. In my opinion, this is the biggest complication. We don't want to force a make:auth
since this will generate way more than needed. We should only need a small oauth/login
page that authenticates the user and asks for its consent. Then a redirect_uri
will be attached to the first response to the client so that it can redirect the user there.Any suggestion before I get started would be more than appreciated.
P.S.: Here is a little table I've made of current solutions for token-based authentication with Laravel.
I've managed to make my last point work by adding an extra passport::login
view when Laravel Passport is marked as stateless
(in a Service Provider).
Passport::stateless();
However, what if users also want the "forgot password" option, etc. I'm starting to think it might make more sense to delegate to Auth::routes()
and simply expect a server-rendered login
page when using grants like the Authorization Code Grant. My only issue with that is that Single-Page Applications will typically end up with two login pages, one that is server-side rendered and one client-side rendered. Any thoughts on this?
@lorisleiva I'd just make Passport stateless by default tbh. I still have a picture in mind how I want to tackle this but probably won't have the time in the upcoming months. Feel free to send in a pr if you want.
Although the original issue was about making the JSON API endpoints stateless it sounds like now everyone is talking about making the authorize prompt and auth routes stateless too?
I Passport is stateless by default how would the authorization code grant work? Is the user expected to log back in every time a token needs to be granted?
Despite what the comment linked to above says, it's considered a best practice to only keep access tokens in memory:
The following measures should be used to protect access tokens:
- Keep them in transient memory (accessible by the client application only).
https://tools.ietf.org/html/rfc6819#section-4.1.3 https://auth0.com/docs/security/store-tokens#don-t-store-tokens-in-local-storage https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTML5_Security_Cheat_Sheet.md#local-storage
As a result, if you are writing a SPA and the user opens a new tab or refreshes the page you need to request a new access token. If you are using sessions on the authorization server it's not a big deal because the redirect happens immediately and the user is not prompted again. If you don't use sessions on the authorization server the user has to enter their username and password again, plus go through any 2 factor authentication prompts etc.
You also are not supposed to issue refresh tokens to browser apps and you should use a short expiration time for the access token, which means you need to go through the authorization flow a lot more often.
Edit: I can see the stateless setup being usable if you only support confidential clients (since they could have long lived access and refresh tokens) but is this a common enough use case that it makes sense for Passport to be stateless by default?
@driesvints Thanks, I will do. Feel free to describe what you have in mind to make sure I implement something that’s more or less expected.
@matt-allan Yes, you make some very good points. First of all, the example I’ve shared above is actually using sessions to authenticate the user on the server side. So if the user clicks again on the login button, they will no longer need to authenticate. For all of the reasons you’ve mentioned, I also think it makes sense to keep that part stateful.
In my previous post I was asking whether we should assume the application scaffolded the entire authentication system using make:auth
or if we should just provide a very simple login page for the user (prior to the authorize page). My reasoning behind this was that stateless APIs would typically not be using make:auth
and would just create their own authentication pages in a SPA. I realise now that this does not make much sense since proper server-side authentication is needed for any grant type that includes redirections.
Regarding refresh tokens and public clients, I completely agree, they should not be used together. If you have a look at the table above, you’ll see that it’s basically one or the other but never both. I also think that the Password Grant (or any grant type that allow public clients) should not return any refresh token when no client secret is provided (and of course allow client secrets to be optional) but I think you’re waiting on oauth2-server for that.
One last thing, in the context of the course I’m building, I am trying the find a go-to secure solution for using Laravel Passport with SPAs. Would you mind if I DM you to run that solution pass through you?
@lorisleiva I was thinking of a way to entirely ditch the csrf requirements and let everything passport related go through API routes. But I don't have time atm to describe/think this over in detail, sorry.
@driesvints No worries Dries that's already helpful. Let us worry about the details.
I've been thinking about how to implement that PR a lot and I'd like to add a few points.
1. The easy bit
First of all, out of the 15 routes provided by Laravel Passport, 12 of them can easily become stateless by simply updating the middleware from ['web', 'auth']
to ['api', 'auth:api']
. Instead of having some kind of Passport::middleware()
option, I think it would be beneficial to expose the routes to the user to provide greater flexibility (more on that on point 4).
2. The authorization routes as-is The 3 routes that are more delicate to make stateless are the following:
GET /oauth/authorize # Shows the authorization page
POST /oauth/authorize # Approves the authorization request
DELETE /oauth/authorize # Denies the authorization request
These routes rely heavily on the web
middleware group for the following reasons:
/login
page when not authenticated (hence assuming the use of make:auth
).authRequest
object in the session when the user is originally redirected and retrieve that authRequest
object from the session when the user made a decision.Here is a diagram illustrating the authorization process as-is. Orange pages are server-side rendered.
3. Making authorization routes stateless If we were to make those 3 authorization routes stateless, it would involve a complete rewrite of the authorization controllers. Since the pages will no longer be server-side rendered, most of its implementation will actually depend on how the client (typically a SPA) handles authorization on which we have absolutely no control.
Here is an attempt from me to illustrate what the authorization process could look like if the login and authorization pages lived in a SPA. Purple pages are client-side rendered (SPA).
As you can see the process is much more complex and we can even end up having to generate a new token within the OAuth process simply to reach the authorize
page.
Also, as @matt-allan mentioned above, without the use of sessions for these authorization routes, the user will end up having to enter their credentials again (plus any potential 2FA prompts) every time their token expires. Refresh tokens can be used to avoid that for confidential clients but not for public clients (e.g. SPAs, mobile applications, etc.).
As a result, my personal recommendation would be not to make these 3 routes stateless out-of-the-box since their implementation would rely heavily on a client on which we have no control. Instead, we need to allow developers to easily override these routes with their custom logic which brings us to the next point.
4. More control over routes
Instead of hiding the 15 routes provided by Laravel Passport in a RouteRegistrar
, we could copy these routes to their Laravel application during passport::install
in a new routes/passport.php
file. The benefits of this approach would be:
prefix
to Laravel Passport.5. CSRF and cookies
One last point that wasn't clear to me when I started using Laravel Passport is how it handles cookies. At first, I saw the TokenGuard
checking for both bearer tokens and cookies and I just assumed that I could choose one or the other when exchanging tokens with clients. However, cookies are only checked to support TransientTokens
generated by the CreateFreshApiToken
middleware and the oauth/token/refresh
route. More importantly, these tokens generated for cookies are never stored in the database and therefore cannot be revoked. Thanks @matt-allan for clarifying this for me.
Even though it might seem out of scope for this issue, I'm mentioning this here because there is a need to ditch the CSRF requirements and rethink the way the CreateFreshApiToken
middleware works.
I think there is a need for cookie-based authentication beyond server-side rendered applications. We could add a cookie
flag to the oauth_clients
table that indicates whether or not we should attach access tokens to the cookies in addition to the usual flow. We could then use a default client flagged as cookie
for storing transient tokens to have more control over them.
This would also be very useful for SPAs that simply want to authenticate their users with credentials and cookies. They could then use a Password Grant client flagged as cookie
(that does not generate refresh tokens for public clients).
I'd love to have your thoughts on that before I start implementing a PR for point 4 and potentially another for point 5.
@a8c71 could you explain how you would use this feature?
If the route to create a client is authenticated with an access token, but you need a client to create an access token, how do you get the access token?
It sounds like you might want dynamic client registration instead? (#804)?
any updates on this? @lorisleiva
@Robert-Hansen if there aren't any new messages then no there aren't any updates. Please don't spam the issue tracker with that and instead try to help out here with a PR.
Hi, I made an improvement to solve this problem but nobody tells me how to share the PR. Can somebody tell me how/where I have to send the PR?
I simply move the routes outside the core to permit a good amount of configuration (like in all new laravel packages) and set up a Features
class to handle the various sections
Thanks
Any update @lorisleiva
Was looking into this again. One piece of the puzzle that remains for me is the question asked by @matt-allan. If anyone could answer that we can maybe further look into this.
Firstly, thank you all for the work done around this.
@a8c71 could you explain how you would use this feature?
If the route to create a client is authenticated with an access token, but you need a client to create an access token, how do you get the access token?
It sounds like you might want dynamic client registration instead? (#804)?
@matt-allan At the time we had several backend clients to our API, each one with their own Client credentials. One of them handled the user accounts and login flow (using SPA/access_token
s). With this particular client the users should be able to create their own Client credentials for their own backends and then authenticate with our API using Client Credentials Grant Tokens.
The approach suggested by @cristitoma should work for my case as my laravel project is backend only, but I see some points raised here in favor of decoupling other parts of the Passport JSON API. IMHO I dont mind some statefullness in exchange of better user experience and faster Passport integration; I'm in favor of @lorisleiva proposed approach.
@lorisleiva @a8c71 I realised I already did the work to move Passport's routes to a dedicated file for the upcoming v11 version: https://github.com/laravel/passport/blob/master/routes/web.php
Now that you have control over them as well as the middleware, does that mean that technically this issue is resolved when Passport v11 is released?
Hi @driesvints 👋
This is not really fresh in my memory I'm afraid but from what I remember it, having control over these routes would definitely help create more custom authentication solutions. Maybe we could close this issue and reference it in different issues if users feel there are still gaps towards stateless authentication using Laravel Passport.
@lorisleiva @driesvints feel free to close this issue as you please, thank you
Cool, we'll close this then. Thanks all.
I want Users using my API be able to create a new Client using the
POST oauth/clients
that uses by defaultweb
andauth
middlewares.Is there any way to specify the
auth:api
middleware to specific Passport routes to allow the use of access_tokens instead of web based authentication?