laravel / passport

Laravel Passport provides OAuth2 server support to Laravel.
https://laravel.com/docs/passport
MIT License
3.27k stars 777 forks source link

Password Grant and independant SPA issue #165

Closed josh9060 closed 6 years ago

josh9060 commented 7 years ago

Hello all,

I have been messing around with Passport and specifically the Password Grant. The reason for this is I plan to build and API with Laravel and Passport that will consume an SPA. The SPA will not be part of Laravel - not built within Blade views.

The main issue I am running in to is that I am required to share both the Client ID and Secret. However, according to the OAuth spec the Client Secret should never be exposed to the end user as it will make the flow redundant I believe.

I believe this is an issue because Passport has not been provided with a way to combat this security issue. I have read that a proxy should be used to inject the Client Secret into the POST/GET request and then sent to the API, however, I am not sure if this is correct.

It would be a great help if anyone had any ideas on how to deal with this issue.

Also, if you would like me to elaborate further please ask!

craigpaul commented 7 years ago

Hey @Joshgallagher, sounds to me like you just have the wrong idea about what oAuth grant to be using. For a SPA (aka anything sending requests from the client side) ou should be using the implicit grant, not the password grant.

Granted it's not in the documentation, but it is in passport, not sure the exact release, but I can find it later if no one else finds it. You can read about the implicit grant here though.

https://oauth2.thephpleague.com/authorization-server/implicit-grant/

josh9060 commented 7 years ago

Hey @craigpaul

I do know about the Implicit Grant but did not know it was available in Passport. Do you have any idea when the docs will be updated? Also, to clear this up - from what I have previously read you should use the Implicit Grant when communicating to a web app/SPA and the Password Grant should be used in mobile apps? I have seen and read that you can use the Password Grant and a proxy for SPAs, as it allows you to use the refresh token due to it being encrypted in a cookie?

Also, if you do not mind me asking, does this not break up the user flow? Logging out a user every hour for example? How does someone like Twitter handle this? Sorry for the constant questions, just trying to wrap my head around this! Also, I have read that Alex Bilbie frowns upon the Implicit Grant and has suggested the Password Grant and proxy method before.

Finally, is there any resources on implementing the Implicit Grant in Laravel Passport?

craigpaul commented 7 years ago

Hey, turns out I was wrong, it never got reopened, my mistake. You are right, you could use the Password Grant like that, but I'm not sure how you would do that. It's much easier to just use the Implicit Grant. The PR that was originally proposed was #63 so I guess we just need to get Taylor to take another look at that and get it merged. I believe his requirements to merge it have been met so it should be good to go.

Again, sorry my mistake about thinking that was already merged.

josh9060 commented 7 years ago

Thats fine, I struggled to find it.

I think the Implicit Grant should be implemented ASAP! I've seen many people implementing the Password Grant incorrectly in live projects which is not good. Also, if Taylor does not want to merge the Implicit Grant PR, then maybe he should look into providing some middleware that can proxy the Password Grant.

Finally, do you think I should go ahead with the original plan I had: using the Password Grant with a proxy or wait/hope for the Implicit Grant PR to be merged?

josh9060 commented 7 years ago

Also, the Implicit Grant is for Third Party applications. An SPA that is created and hosted by the API creator would mean that the SPA is trusted, therefore making it a First Party application right? So, wouldn't using the Password Grant proxy method be a better approach for someone wanting to treat their SPA like a First Party app?

I think we need @taylorotwell input on this matter. I think this area needs to be addressed because people can so easily misuse Passport and the Password Grant Type.

craigpaul commented 7 years ago

@Joshgallagher Personally, I would see what can be done about the implicit flow, but one thing you could do (if you have a backend for your SPA that isn't your API) is submit your AJAX request to your own backend then follow the Password flow from your server. That way your secret is kept secret and you can just use Guzzle (or something similar) to request your access token with the proper password grant flow and receive an access token that you can send back to the client.

josh9060 commented 7 years ago

What you are proposing is to create a route where I would post my credentials, and then have Guzzle inject the Client Secret and post them to the OAuth route provided by Passport? If so, that would work. However, the problem here would be that I am then storing the Token and Refresh Token in local storage. I amy have to return the Tokens, but in an encrypted cookie.

I would wait for the Implicit Grant Type, however, I believe it breaks up the flow of your application. So, maybe implementing the proxy with the Password Grant may be better for usability in my case. The only problem here is that the packages that previously done his are deprecated, and I would not trust myself to create something robust and safe :/

craigpaul commented 7 years ago

Ya I understand the hesitation on that path. Yes perhaps that way is better, I've never seen that done personally though, do you have any resources on that topic?

josh9060 commented 7 years ago

Well Alex Bilbie previously suggested it, I will have to find the article. Also, a package was created for this method and it references what Alex said.

Package: https://github.com/thinkingmik/api-proxy-laravel

craigpaul commented 7 years ago

@Joshgallagher I believe I found the article you were referring to, it's no longer accessible on his website, but you can use the wayback machine and find it. Let me know if this is what you're referring to. I'll check out that package btw, thanks!

Article: https://web.archive.org/web/20141208132104/http://alexbilbie.com/2014/11/oauth-and-javascript/

josh9060 commented 7 years ago

Yes, that is the correct article. I think this method would be appropriate for First Party app and the Implicit Grant for Third Party apps.

craigpaul commented 7 years ago

@Joshgallagher for sure, thing is though that Passport has its own implementation (ish) of this idea. The CreateFreshApiToken middleware implements this idea essentially, but it requires a Laravel back end to serve the front end. I realize that your first post says you aren't serving it with Laravel, but is there a reason for that? If you serve it with Laravel you can use that middleware to attach a cookie to your response and then use that cookie and a csrf token to consume your own API from the front end. Now if you simply can't use Laravel to serve your front end, you might be out of luck, but if you are, this should definitely solve your problem. If you have any questions please feel free to ask :)

Modelizer commented 7 years ago

@Joshgallagher Even I'm building a SPA but my solution is a little bit different.

  1. I extracted SPA from whole Laravel framework because I believe PHP is not required in SPA
  2. I use personal access token when a user is registered. This way I don't need client id to send.
  3. Both the instance are independent (SPA and API). SPA is built on top of Laravel package.json (With few more modification) and including such as vuex, vue-router etc.

@craigpaul @Joshgallagher Let me know if this idea has a flaw or we can more enhance it.

Also, I'm looking forward to create a SPA framework on top of Laravel package.json and name it as Laravue. This will help people to get started quickly.

josh9060 commented 7 years ago

@craigpaul I wanted to separate the SPA from the Laravel API backend for future changes. For example, I want to move from Vue to React. Also, I think it's a better design pattern to separate your applications and not have them shoved into one huge application. I just hope the Implicit Grant is added soon.

craigpaul commented 7 years ago

@Joshgallagher I was suggesting a completely separate app as well (two different Laravel backends), but ya I understand where you are coming from. Unfortunately then without a Laravel backend that shares the oauth keys and a database with your API you are limited to the Implicit Grant (in my mind anyway). You could go with the route that @Modelizer suggested though, but I do see a few potential flaws with that.

@Modelizer so...by issuing those personal access tokens you are essentially giving them "unlimited" access to your API and if compromised, the attacker would have full access as the token will never expire automatically, you would have to revoke that token manually. That being said it depends what type of data you are exposing, if nothing is super private or sensitive maybe it doesn't matter that much. You could also implement proper scopes for them and then that would be a bit more secure in a sense. In my mind the biggest flaw is the fact that they never expire, so if that isn't a concern for your app(s), then it should be just fine for a simple implementation.

Modelizer commented 7 years ago

@craigpaul Yeah even I was thinking the same :+1:

I decided to expire personal token in every 15 days via scheduler and re-issue a new token for every login. Regarding unlimited access, the user will only be able to control only his stuff.

matthewlilley commented 7 years ago

I would be interested in what @taylorotwell has to say on this also.

josh9060 commented 7 years ago

I would be interested also. As I have said either the implicit grant should be imemented or some kind of middle ware that allows the password grant to be used.

matthewlilley commented 7 years ago

@Joshgallagher The only way I can see this working for public clients, most importantly native apps that aren't running on web-server, is by setting up a new endpoint on the API such as /api/login, which would add the client id and secret to the request and then forward it to the oauth/token route, returning the response.

taylorotwell commented 7 years ago

I feel like we had a PR for implicit grant recently. Am I imagining that? I can't find it.

taylorotwell commented 7 years ago

Ah here it is: https://github.com/laravel/passport/pull/63

We just need it to sent to the 1.0 branch.

matthewlilley commented 7 years ago

@taylorotwell

After reading https://alexbilbie.com/guide-to-oauth-2-grants/

It seems that implicit grants are only used for 3rd party clients.

"If the client is a web application that has runs entirely on the front end (e.g. a single page web application) you should implement the password grant for first party clients and the implicit grant for a third party clients."

If I have a 1st party native mobile app for my api, I should be able to send only the username, password, client id and password grant type to /oauth/token. Right now that's not possible without attaching a custom middleware to oauth/token.

matthewlilley commented 7 years ago

Here's an example of somebody getting around this issue.

route override

https://github.com/Team-Tea-Time/thaliak/blob/dev/app/Providers/AuthServiceProvider.php#L37

and middleware

https://github.com/Team-Tea-Time/thaliak/blob/dev/app/Http/Middleware/InjectPasswordGrantCredentials.php

matthewlilley commented 7 years ago

Pinging @themsaid - I saw the article you wrote on passport, and grant types. What's your thoughts on this?

If I have a 1st party native mobile app, I should be able to only send the client_id, username, password and grant type of password to the oauth/token endpoint.

taylorotwell commented 7 years ago

Is someone able to PR a fix for that or no?

josh9060 commented 7 years ago

@matthewlilley For SPA's we should create a proxy middleware where the Client Secret is injected into the request, and thus a HTTPS cookie is returned. It is bad practice to store an access and refresh token in HTML5 storage.

Also, for mobile apps I would suggest the Password Grant with the exact same proxy middleware. The Implicit Grant create a bad user experience and as you said @matthewlilley it is for 3rd party clients.

Finally, this package was created to aid this problem: OAuth2.0 Proxy Package. This flow was suggested by Alex Bilbie.

adiachenko commented 7 years ago

We have talked recently about this problem on laracasts forums. You can look up examples there until someone will come up with a better implementation.

I feel like this is something that should be addressed as most front-end developers prefer to decouple single-page application from their backend (i.e. Laravel). There is a lot of confusion in the community regarding this issue and what I've seen most beginners do is to just gravitate towards CreateFreshApiToken middleware and scratch their head on how they should stitch it up with their separate SPA, lol.

josh9060 commented 7 years ago

@adiachenko I agree, I have seen many beginners create unsafe apps that are extremely vulnerable. I do agree this has to be solved, as this is key functionality for many developers who create separate SPA's from their API's!

Riari commented 7 years ago

I've been thinking about this and how it relates to Passport, and I think it would help to step back a bit and consider what the fundamental problem is.

Here's the way I see it - please do correct me if any of this is wrong:

The key thing about that last point for me is that "hiding" the client secret isn't inherently more secure because all it does is prevent the need for anyone to discover it in the first place. The workaround that injects it, for me, is more a matter of convenience. It's also true that exchanging a user's credentials for an auth token is in itself no less secure than conventional PHP session/cookie auth - the important thing is how you handle the token after receiving it (plus, let's face it, HTTPS should be encouraged regardless).

So the bottom line for me is that regardless of the OAuth spec and Passport's implementation of it, ultimately we just need an auth endpoint that can be used to exchange user credentials for a token, which can then be used to authorise requests to access/modify/delete protected resources (depending on whatever scopes or permissions may be in play). I'm not sure if this is necessarily a Passport concern since Passport doesn't handle users in any direct fashion, it only asks the application to look them up - but since it's an OAuth service, it makes sense that we'd want to use it to generate tokens.

Leaving out the whole "how should tokens be handled/stored client-side" debate, which I feel is neither a Passport nor a Laravel concern, what are the solutions? Maybe we should be able to write our own auth route/controller action, or adapt Laravel's scaffolded auth, to request a token from Passport programmatically? Is there an OAuth grant type more suitable for this that Passport doesn't already implement? Something else? What do we fundamentally want out of this?

Riari commented 7 years ago

OK, having looked at this some more since my comment above, I can see that Passport does in fact implement all of OAuth2's grant types and the issue at this point seems to be entirely down to the lack of cookie support. Sorry for clouding the issue!

So we want to enable optional support for cookies, which #193 achieves but was closed due to the complexity of its approach. Is that right?

Another workaround for this (not a solution for Passport itself, but something I'm considering doing in the short term) is to implement half of that PR in your project (the cookie generation and encryption) and write another middleware class that extracts and decrypts the token from the cookie and injects it into the request. Not ideal, but it would involve less overriding of Passport components.

Modelizer commented 7 years ago

As @themsaid suggested keeping Passport as simple as possible. I started working on two projects.

  1. Single Page Application Framework
  2. Bankend API for SPA

I do see there is a long roadmap to complete. Also, I'm looking forward to implementing what @Joshgallagher and @craigpaul suggested for SPA security but before that, some fundaments should be completed prior.

It would be a good help if @Joshgallagher and @craigpaul contribute to making these projects more reliable and support for a long term.

Riari commented 7 years ago

I'm still not convinced that developing solutions separate from Passport is the right way to go. Obviously the stuff you're developing @Modelizer is probably going to be useful to a lot of people, which is great. 👍 But talking about #193 specifically, the verdict seems to be that it should be implemented as a package on top of Passport. Sounds fine in principle, but unless I'm mistaken, that package would have to do the following:

  1. Provide middleware to attach an encrypted cookie to responses from Passport's token/refresh endpoints
  2. Provide middleware to handle grant parameter injections
  3. Override route definitions to add the middleware as needed
  4. Override the guard, which means overriding at least one method in the service provider, in order to make it support cookies

Seems cumbersome to me and more complicated than the PR itself. I don't know about anyone else, but I would be inclined not to use such a package and instead use the middleware solution I mentioned in my previous comment.

So I think it would make sense to work on the PR until the implementation is acceptable, unless @taylorotwell is against adding cookie support in general.

Modelizer commented 7 years ago

Suppose if we added above points and made Passport SPA compatible, then in near future another SPA related things will arise? This way Passport will become SPA centric. It would be better a separate team will take care of SPA and Laravel internal team will focus on building and enhancing Passport. They also need to maintain other packages as well.

Passport is like a bootstrap you need to build your application specific logic on top of it. Right now my focus is SPA on top of Passport and Laravel.

Riari commented 7 years ago

I disagree. For the most part, we're only talking about the addition of cookie support for the Password Credentials grant (or Resource Owner Password Credentials grant, as it's called by the OAuth2 spec), which isn't even SPA-specific - it just happens to solve a problem for decoupled SPAs, which are becoming increasingly popular and no doubt a common use case for Passport.

I'm personally happy to implement a solution in my project; I just think that if a proper solution is going to be developed, doing so in Passport would make a lot of sense, not least because of the overriding another package would currently have to do to achieve it. It would be a shame to go to that length when all we're really asking is "can we use cookies for other grant types?" and it's already implemented for the implicit grant.

Taylor's call though, obviously. If he doesn't want any more options/config/complexity being introduced, people are free to decide how they want to tackle this problem on top of/outside of Passport.

mkalantar commented 7 years ago

I implemented two new class PasswordGrantTokens and PasswordGrantTokenFactory like those for personal token. adding PasswordGrantTokens trait to User class so createToken function is available and issues password grant tokens. token are created internally and not via oauth/token url. the reason i prefer password grant to personal tokens is that password grant tokens have refresh token and life time is configurable. now to handle expired token and issue new tokens using refresh token in server side, not in client side, TokenGuard should change. should i define new guard or make a PR or any else idea?

nickkuijpers commented 7 years ago

Hi,

I am also in need of some workflow / method to decouple the SPA from the API. I have it included in the blade views so on first load we get the csrf token but i am still unclear about vulnerabilities..

I think decoupled front-end and back-end will be the future because of like Vue Electron etc but i havent found the perfect solution yet.

Are there any new experiences?

raigu commented 7 years ago

Hi,

I also want to separate front-end and back-end.

Thanks to Laravel I have dived into Vue.js and started to like it. It is easier to develop front-end in an environment dedicated to it. But I do not want to bring my business logic into front-end and Laravel is a good choice for back-end. I think that the more Laravel developers are getting to know the Vue.js the more decoupled SPA application will be.

Reading all previous posts and doing some research I am not sure if the Passport is right solution for simple separated SPA. I found this statement

The OAuth 2.0 specification defines a delegation protocol that is useful for conveying authorization decisions across a network of web-enabled applications and APIs. OAuth is used in a wide variety of applications, including providing mechanisms for user authentication. This has led many developers and API providers to incorrectly conclude that OAuth is itself an authentication protocol and to mistakenly use it as such. Let's say that again, to be clear:

OAuth 2.0 is not an authentication protocol.

The title of OAuth 2.0 standard RFC6749 says The OAuth 2.0 Authorization Framework.

I was starting to think that maybe I am having difficultis in my SPA because I want to solve problem with Passport that is not in the scope of OAuth2? Most user here are struggling how to do authentication. In Laravel's documenation Passport is covered in topic API Authentication. But OAuth2 main purpose is to deal with authorization. I do not consider myself an expert in OAuth2 but isn't here some misunderstandings in concept level?

I understand that Passport is suitable in situations where authorization is complex. A lot of clients through which same user can grant different permissions to different clients (there the client secret makes sense). But if you have simple decoupled SPA and most of the time you do not have to check permissions, just to ensure if user is logged in and return proper data based on Auth::user() object then you do not need complex solutions. Some simple token authenticatin solution (for example like this) will do. If you need some authorization then Laravel's gates and policies can help out.

If all you have is a hammer, everything looks like a nail. I am afraid that the Laravel documentation is giving a wrong impression that if you need protected API then Passport is the only right tool to use. In documentation there is topic API Authentication (even so the OAuth2 is meant for authorization). No other alternative. If this is the case then I am afraid there would be a lot of confusion and frustration about this topic in future.

If I am on right track with my conclusions there should be more simpler and comprehensive token based solution for API-s consumed by decoupled SPA-s and/or (maybe it exists but I have not figured it out yet) explicitly documented in Laravel's documentation (or at least referred to some solution, or added some statement in API Authentication topic to prevent missuse). Passport will come handy if complex authorization issues emerge. Mean time simpler solutions would be more reasonable. Of course, Laravel can not be opinionated but I think in the light of the raise of Vue.js (and SPA) popularity it will prevent a lot of problems in community.

nickkuijpers commented 7 years ago

Hi @raigu,

Good item, thanks!

I've solved it with Passport the following right now;

Using Password Grant with the client id in the client side code, viewable for everyone. I read a lot about hiding the client id but i think this is only required if you do not use password grant.

I've following this course; https://www.youtube.com/watch?v=2eEok752-qc&list=PLkZU2rKh1mT81nK9LgCV8l7kuSr6Xoj2x

I first thought it was not the good solution because we are importing al OAuth2 functionality but at last it seems to work very well and stabile and ready for the future to add external functionality.

Snippo commented 7 years ago

Is there any best practice on this yet?

As I understand it (correct me if I am wrong) in conventional OAuth2 workflow you authenticate an user on the server which then authorizes clients to access their data. So in theory this could mean that the user could even block the SPA from accessing their data (which would make no sense for the purpose discussed here).

Instead, one client is created (password grant client) which manages the data of all users on the server. The client requests the username and password and returns the token such that the user can access their data.

The client itself also requires authentication which I believe is the problem which is discussed here. However, I am wondering if it is really a problem if the client secret is not hidden. Obviously, anyone or anything will be able to access the 'manager', but it will not do anything unless they provide some valid credentials for a certain user. In worst case, a user will be able to access their own data through your API in their self-developed client. Correct?

Edit: This is assuming that API calls can only be made from the same server. Otherwise I guess the user data can be stolen by spoofing the client.

Edit2: Which also prevents self-developed clients from being used. So I guess the password grant is safe to use but maybe too complex for the purpose of a SPA (as @raigu also explained)?

nickkuijpers commented 7 years ago

@Snippo, i have not used any other options in the latest projects than the password grant of Passport. I've been diving into https://github.com/tymondesigns/jwt-auth but i stay with the Passport solutions because of scalability. Do u have any new xp?

Snippo commented 7 years ago

I also looked into JWT but decided to use passport as it is built-in and should provide the same functionality. I got it to work, but I want to be sure it is secure. That's why I was hoping to get some best practice information here

nickkuijpers commented 7 years ago

@Snippo haha me too, i think as long as you use password grant in a environment where you use the CSRF it will be secured. For example, when we do not use the CSRF other people can clone our single page app and use the same API's to authenticate but if we use CSRF and CORS i think we have secured it.

About showing the client secret i am still unsure about, if we use the password grant i think it will be no problem but i've been trying to make a proxy for it so its not visible but they will be able to communicate to that proxy also..

nickkuijpers commented 7 years ago

@Snippo i've found a nice resource of information about this; https://github.com/thinkingmik/api-proxy-laravel

nickkuijpers commented 7 years ago

I dont think that will solve it either because unauthorized people can just like make api requests to that proxy instead of the database itself and it will not change any permission..

Snippo commented 7 years ago

It is like a locked door I guess. You can either give everyone the key or unlock it yourself. The result is the same: anyone can get in.

nickkuijpers commented 7 years ago

Yes but for example when lets say you add a proxy, which adds to client_secret to the request, they will be able to use the api routes either way. If we do not use a proxy, they just trigger that api route and other wise with a proxy they will trigger the proxy api method.

To solve the issue about not making the client_secret public, i've used the following method just now.

<?php

namespace App\Http\Middleware;

use Closure;

class InjectClientSecretToRequest
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // Validate if the request is a password grant
        if ($request->grant_type == 'password') {

            // Then adding the client secret so its not publicly visible
            $request->request->add([
                'client_id' => config('app.client_id'),
                'client_secret' => config('app.client_secret')
            ]);
        }

        return $next($request);
    }
}

And add it to the middleware kernel

protected $middleware = [
        //
        \App\Http\Middleware\InjectClientSecretToRequest::class,
    ];
Riari commented 7 years ago

Injecting the client secret is really just a matter of convenience. It has zero bearing on security; all it does is remove the need for a malicious user to discover the secret.

I don't think this is any cause for concern since the OAuth 2 spec doesn't actually require client credentials to be included in password grant requests - only the grant type, username and password. It's just that Passport's implementation does for some reason.

nickkuijpers commented 7 years ago

Hi @Riari

Ah perfect, i will keep it this in the .env for only password grants. Thanks for your respond!

Snippo commented 7 years ago

Here is my implementation of the password grant: https://github.com/Snippo/Auth

I think the only security risk is if someone manages to 'steal' an access token (which I guess could be prevented by using https). Please let me know if there are any flaws to this implementation.

mesqueeb commented 7 years ago

Hello community. Here I am another two months later, finished building my SPA which stands alone from Laravel. I'll be wrapping my SPA to a mobile app, and want my users to be able to log in and retrieve their data. My backend will always be Laravel ♡. But I want wondering is there any good way that has already solved this issue?

I've read through the entire thread and learned a lot. @Snippo just made something in the post above, but it still needs some documentation and I'm too new to understand it. : D @nickkuijpers also solved the issue by following the Youtube guide, which I will try now, but I'm using VueJS not angular, so I'll do my best to understand!

I wanted to address some other people who were really active here: @Joshgallagher @craigpaul @Riari

Is there anyone who has found a solution or a simple way to make a login system on a JS only SPA with Laravel running on a server?