Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Associate client_credentials grant to a user #143

Closed
ghost opened this issue Oct 15, 2016 · 48 comments
Closed

Associate client_credentials grant to a user #143

ghost opened this issue Oct 15, 2016 · 48 comments

Comments

@ghost
Copy link

ghost commented Oct 15, 2016

Hello

Currently, I'm building an API where my users will be able to create their API keys from my platform and then use them to perform requests, I look for OAuth 2.0 (given that in the future I'll support the Authorization Code and I have a mobile application that uses the password grant), and for this case I want to follow PayPal authentication system (https://developer.paypal.com/docs/integration/direct/paypal-oauth2/).

But I have an issue trying to use the client_credentials grant, based on the RFC 6749 of OAuth 2.0:

4.4. Client Credentials Grant

The client can request an access token using only its client
credentials (or other supported means of authentication) when the
client is requesting access to the protected resources under its
control, or those of another resource owner that have been previously
arranged with the authorization server (the method of which is beyond
the scope of this specification).

If I understand correctly, the client credentials can be associated to a resource owner or can have a null user instance to access any public resources available.

In my case, I want that the client_credentials are associated to a user in my system, and then, the access token can be related to it, but currently, the ClientCredentials grant sets the user instance as null.

I thought that one solution was create a new Service Provider based on the PassportServiceProvider, create a custom client credentials grant and then in the new service provider, enable the grant, but I don't want to keep maintaining the new ServiceProvider with the latest changes of PassportServiceProvider.

The other solution was to create a new middleware to check if the access token has the client_credentials grant, then, I'll look up for the client_id and then, get the user id, but I found this not useful because the JWT token should have this information.

I'm not sure which path to follow or if I miss something.

Thanks

@dreferreira
Copy link

I've done a little bit of investigating into this using the password grant as noted below. I hope you find it insightful. Warning it's fairly long
#151

@themsaid
Copy link
Member

@OscarRPR the client_credentials requires no user, it gives access to publish resources that aren't owned by any user, so a user permission is not required.

@ghost
Copy link
Author

ghost commented Oct 22, 2016

@dreferreira Thank you, I'll keep an eye on it to check for a solution.

@themsaid But, if you read the OAuth 2.0 specification in the original post says:

The client can request an access token using only its client
credentials (or other supported means of authentication) when the
client is requesting access to the protected resources under its
control,

Which if I understand correctly, it means that the client can request access to his own resources, then, we will need a way to configure it, no?

Or, as I mentioned in the post, PayPal says that he uses OAuth 2.0 with client_credentials grant, and with the keys I generate I can perform calls for my account on protected resources, then, they're not really using OAuth 2.0?

@themsaid
Copy link
Member

Check https://oauth2.thephpleague.com/authorization-server/client-credentials-grant/

It's not meant for that grant type to be associated with a user, you can use any of the other grant types based on your condition.

@telkins
Copy link

telkins commented Jan 6, 2017

@themsaid Your information here has been helpful, but I think that one point that you're not addressing is that many companies, including PayPal, provide APIs that use OAuth2's client credentials grant that makes a distinction between different clients (as identified by their client credentials, client ID and secret, of course).

So, perhaps a better way of asking about this would be something like how can one use Passport and client credentials grant type in order to provide an API like PayPal does, where each client ID and secret unlock access to the related client's resources?

I've been looking for a solution myself for a small hobby project and I've come up with the following solution:

  1. Create an OAuth 2 client for each distinct set of resources. (They can be tied to Laravel's users however they need to be. For my own purposes each user would be able to have 0 or more clients and I would introduce a relationship/pivot table to keep track of the client-resource set relationships.)
  2. When an access token is requested using a client credentials grant type, then it's issued as usual.
  3. When an API call is made, I use the client ID based on the request's access token to match the related resource set.
  4. All requested API calls/operations take place within the context of the related resource set.

Does this sound reasonable?

So....to do this, I need (or would like help with) the following:

  • I need to be able to programmatically generate OAuth 2 clients. I believe this can be done via Laravel\Passport\ClientRepository::createPasswordGrantClient().
  • I can use the newly created client to tie the client to the related resource set.
  • I need to determine the client or token on each request. I have been able to do this, but it's duplicating work found in League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator::validateAuthorization(). (Right now, I'd implement this in a piece of middleware that would find the client and/or token and attach it to the request.) This shouldn't be necessary, in my opinion. There should be an easy way to get the client or the token (which provides the client). Is this possible already? Should something like this be added to Passport?

I would certainly appreciate any feedback/suggestions you might have.

@aindong
Copy link

aindong commented Feb 7, 2017

I also need this kind of implementation, where users can generate their own secret key and use those secret_keys into their third party application that can't redirect. The user/partner will use client_credentials flow/grant type and I want to be able to get who owns or what user owns that access_token created by client_credentials grant. Is there any way to do this?

@Pierolin
Copy link

@telkins @aindong @themsaid I am using client_credentials as grant type for my API too. Any good news for how to get Client Id of the current API caller.

@telkins
Copy link

telkins commented Mar 30, 2017

@Pierolin Here's what I do to get the client (and the token), which I'm currently doing in middleware in one project and making it available via the request:

    protected $clientRepository = null;
    protected $tokenRepository = null;

    public function __construct(ClientRepository $clientRepository, TokenRepository $tokenRepository)
    {
        $this->clientRepository = $clientRepository;
        $this->tokenRepository = $tokenRepository;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $jwt = (new Parser())->parse($request->bearerToken());

        $request->offsetSet('passportClient', $this->clientRepository->find($jwt->getClaim('aud')));
        $request->offsetSet('passportToken', $this->tokenRepository->find($jwt->getClaim('jti')));

        return $next($request);
    }

I then do the following in my controllers:

        $client = $request->get('passportClient');

This all feels rather hackish to me, but I don't know what else to do and I don't have time to dig deeper to find a better solution. In the meantime, though, this works, the code is relatively clean and straightforward, and it's fairly isolated, so I'm not worrying about it too much...yet. I do hope that someone comes up with something better and less clunky. :-)

Hope that helps....and if you see a better way, please share...! :-)

@telkins
Copy link

telkins commented Mar 30, 2017

@Pierolin Sorry...I should have included my use statements so you don't have to figure out what classes are being used:

use Closure;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\TokenRepository;
use Lcobucci\JWT\Parser;

@Pierolin
Copy link

Pierolin commented Mar 31, 2017

@telkins Thank you so much for the helpful solution.
Base on your idea, I insert 2 methods into the parent controller class Controller as following.

`protected static function getCurrentClient(Request $request){
$clientRepository = new ClientRepository();
$jwt = (new Parser())->parse($request->bearerToken());
$client = $clientRepository->find($jwt->getClaim('aud'));
return $client;
}

protected static function getCurrentToken(Request $request){
    $tokenRepository = new TokenRepository();
    $jwt = (new Parser())->parse($request->bearerToken());
    $token= $tokenRepository->find($jwt->getClaim('jti'));
    return $token;
}`

Then call them in extends controller.

`$client = $this::getCurrentClient($request);

    $token = $this::getCurrentToken($request);`

@telkins
Copy link

telkins commented Mar 31, 2017

@Pierolin You're welcome. I'm glad I could help.

I would also make a suggestion: you might consider adding the two methods to a trait instead...composition over inheritance. :-) I think I'm going consider dropping the middleware solution and create a trait that does this instead. It's a little more flexible and it's only used when needed (where with my middleware solution, it's done on every request).

@Pierolin
Copy link

Pierolin commented Mar 31, 2017

@telkins Great suggestion. It is more flexible and loose coupling. I am interested in what name you will give it to the trait.

@telkins
Copy link

telkins commented Mar 31, 2017

@Pierolin LOL! I'm not that great with names. I would probably go with something like GetsOAuth2ClientTrait...but I've changed my mind and will likely refactor my middleware into a simple service provider.

Going with a service provider will take a little more time, but it kind of makes a little more sense. Plus, with a trait, if it's used across multiple classes, then it's likely the same effort to retrieve the information will be repeated each time. With the middleware or with a service provider, this won't be the case.

So...while I would like to go with something like My\Namespace\PassportServiceProvider, that would probably be a bit confusing in some places, so instead I'm going forward with something like My\Namespace\OAuth2ServiceProvider. This will have two methods to begin with:

public function getClient();

public function getToken();

I may build more into it as needed. Of course, by the time I get around to finishing something, it's likely @themsaid or someone else will have built something like this -- or better...! -- into passport directly. ;-)

@telkins
Copy link

telkins commented Jun 30, 2017

@Pierolin Hey...it's been a while, but a colleague of mine at work found the following blog article, which appears to be a very nice way of solving our little problem: http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/

You may want to read it through and think about using a similar solution, modified for your own personal needs, of course. I'm pretty sure that I'm going to update my solution to be more in line with the approach the blog article author suggests.

Take care....

@johnellmore
Copy link

@themsaid You cited The PHP League as the source for saying the client credentials grant doesn't support user authentication. I assume you're citing this paragraph:

This grant is suitable for machine-to-machine authentication, for example for use in a cron job which is performing maintenance tasks over an API. Another example would be a client making requests to an API that don’t require user’s permission.

This paragraph is just giving an example use case, not an exhaustive description of what client credentials grant is intended to do.

In any case, The PHP League isn't the authoritative source for how OAuth2 works. As @OscarRPR said, the official OAuth2 spec says that client credentials grant can give a client access to its own resources, which implies user association of some kind.

So with that said, is introducing user associations/authentication for client credentials grant something that Passport might introduce support for?

@driesvints
Copy link
Member

Having tried some scenarios out myself, I can see how this can be beneficial. If I get this right you basically want to retrieve the associated user who created the client when making a client_credentials grant token request so you can limit the returned resources based on the user who created the client?

@mattkoch614
Copy link
Contributor

mattkoch614 commented Oct 17, 2018

@driesvints - I am having a similar issue to the one described in this thread. My scenario matches exactly what you mentioned above - e.g. that we want to limit the returned resources based on the user who created the client.

Having said that, we are also open to using a different grant type if that seems more appropriate.

edit - The code snippet in #124 seems to solve this. Perhaps we can add as a PR somewhere?

@driesvints
Copy link
Member

Yes although I'm not sure we need the jti header. I thought there was also a header to identify the client directly. Can't find it at the moment. Feel free to send in a PR.

@mattkoch614
Copy link
Contributor

I'm fine removing the jti header if another one makes more sense.

@driesvints
Copy link
Member

I think it's the aud claim.

@mattkoch614
Copy link
Contributor

Just looking through a sample dump of what's contained when I parse the headers of the bearer token (where $request is a Laravel Form request) here's what I get:

$bearerToken = $request->bearerToken();
$bearerTokenHeaders = (new Parser())->parse($bearerToken)->getHeaders();
dd($bearerTokenHeaders);
array:3 [▼
  "typ" => "JWT"
  "alg" => "RS256"
  "jti" => EqualsTo {#1083 ▼
    -name: "jti"
    -value: "f23f7d2e6aff68c25c1f62b3312758dc71b5c1e4fa23988ab686154acf941df2388d9cd251eab0ae"
  }
]

I don't see aud anywhere in here or in $request->headers() either.

@driesvints
Copy link
Member

Ah yeah, could be mistaken. I'm sorry, I don't really have time to look into this now. Let me know what you might find.

@mattkoch614
Copy link
Contributor

OK, so there are a few ways to do this:

Option 1: using the aud claim on the bearer token

$bearerToken = $request->bearerToken();
$clientId = (new Parser())->parse($bearerToken)->getClaim('aud');
$user = User::find(Client::find($clientId)->user_id);

Option 2: using the jti header from the bearer token

$bearerToken = $request->bearerToken();
$tokenId = (new Parser())->parse($bearerToken)->getHeader('jti');
$client = Token::find($tokenId)->client;
$user = User::find($client->user_id);

Any thoughts on which is preferable? Option 1 seems like it may be simpler.

@driesvints
Copy link
Member

Yeah I think Option one if the one you need. Also: it might be worth to add a user relationship on the Client model which will make it easier to retrieve the user who created the client.

@mattkoch614
Copy link
Contributor

Great idea @driesvints - I actually tried to just pull out the user from the client model using a ::with('user') and realized it wasn't there.

@telkins
Copy link

telkins commented Oct 18, 2018

Yeah I think Option one if the one you need. Also: it might be worth to add a user relationship on the Client model which will make it easier to retrieve the user who created the client.

Sorry...I'm playing a bit of catch-up, but I think this is one of the things that made sense to me "way back": being able to access the user via the client.

Btw, it's nice to see some momentum/traction now. Thanks @driesvints and @mattkoch614...! :-) This definitely feels like something that's been missing from Passport and I'm excited to see some movement. :-)

I'll check in later and see if I can get my mind back into this and perhaps help out a bit. It's not my area of expertise, but if I can learn and/or contribute, then I'll be happy. ;-)

@mattkoch614
Copy link
Contributor

mattkoch614 commented Oct 18, 2018

Quick question to either @telkins or @driesvints - I want to write some backing tests for adding this functionality (both the ability to grab the user from the client in an elegant way) and adding a User relationship on the Client model. I'm hitting a bit of a snag setting up the world - one example being the following, which doesn't function in a test because there is no config helper method available in the package (it's assuming one exists in a Laravel installation):

src/Client.php

/**
* Return the user associated with the client.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
    $provider = config('auth.guards.api.provider');

    return $this->belongsTo(config('auth.providers.'.$provider.'.model'));
}

Bear with me as I haven't contributed to a Laravel package before...

@telkins - It sounds like you have some ideas, perhaps we could chat? If Passport provided the ability to perform Option 1 above, in what way would you like to be able to leverage it (for example, from inside a controller)?

I think it would be kind of nice if, when Passport is being used, the client and token are simply made available via the request object. That being said, I see the downside in adding this to every request if it's only applicable to a handful of routes. You could probably still get away with doing this via middleware if you added a check for a bearer token on the request object, which would only add keys if it was present.

@driesvints
Copy link
Member

@mattkoch614 I see that the Token class has the exact same method and doesn't have a test so I think it's safe to send it in without a test for it.

But you might want to add a test for retrieving the client. I wouldn't add it to the request as Passport doesn't hooks in functionality into the Request object at this point. I also think a middleware isn't appropriate for this. You can already use the ClientRepository for thus:

public function fooControllerMethod(Request $request, Parser $parser, ClientRepository $clients)
{
    $bearerToken = $request->bearerToken();
    $clientId = $parser->parse($bearerToken)->getClaim('aud');
    $client = $clients->find($clientId);
}

Unfortunately I don't have too much time to think on this at the moment. I'd just scan through the library to see what the most appropriate place would be to add the functionality to retrieve the client from the token.

@mattkoch614
Copy link
Contributor

mattkoch614 commented Oct 19, 2018

@driesvints @telkins OK, I gave this a shot. Take a look at the PR, and let me know your thoughts.

Updated PR #851

@telkins
Copy link

telkins commented Oct 19, 2018

@mattkoch614 I'm not sure I'm the best person to help here, but I'll do my best.

As for testing packages in general, you may want to look into https://packagist.org/packages/orchestra/testbench. I've used it a bit in the past. It takes a little work up front to get things wired, but then it's quite nice because you have the application environment in place that you need.

You can also look for solutions specific to the problem with config() not being available. I just saw this the other day, for example: https://akrabat.com/replacing-a-built-in-php-function-when-testing-a-component/

I've seen other solutions, but have rarely had the need to use any of them as I usually have a "complete" environment in place for tests.

@telkins - It sounds like you have some ideas, perhaps we could chat?

Thanks. I'll do my best. :-)

If Passport provided the ability to perform Option 1 above, in what way would you like to be able to leverage it (for example, from inside a controller)?

I'm not sure what the best way would be. Ideally, I think things should be seamless. That is, my main concern is getting the current user. So, wherever I would normally access that information, it should be the same. (If that's too vague, let me know and I'll try to drill down a bit.)

As I stated in one of my earlier posts, I wanted "to determine the client or token on each request." So, getting that done without "duplicating work found in League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator::validateAuthorization()" would be a good first step, IMHO.

After that, I think I would want to explore how authentication works elsewhere when a user is "set" within the Laravel framework. This would be the next thing to be done. (I don't know the details here, so I can't speak to that....but I imagine parts of Passport are already doing the same kind of thing.

I think it would be kind of nice if, when Passport is being used, the client and token are simply made available via the request object. That being said, I see the downside in adding this to every request if it's only applicable to a handful of routes.

I think that would be nice, too, but it also seems like it would be forcing something that doesn't really fit just for the sake of convenience. Perhaps some Passport-specific methods a la Passport::client() or something along those lines...?

You could probably still get away with doing this via middleware if you added a check for a bearer token on the request object, which would only add keys if it was present.

I've gone this route before, and like you said, "I see the downside in adding this to every request if it's only applicable to a handful of routes." I'm not sure that it's a big deal, though. It feels inefficient to me, but it's almost certainly not as bad as I imagine. Anyone got any input on this...?

OK, I gave this a shot. Take a look at the PR, and let me know your thoughts.

Nice...! I will try to take a look later this evening. My day's been wrecked with some unexpected fires that needed to be put out. Friday, right...?! ;-) Anyway, I'll check it out and get some feedback as soon as I'm able.

@telkins
Copy link

telkins commented Oct 19, 2018

@mattkoch614 What I meant by seamless was that the user should be retrievable as described here: https://laravel.com/docs/5.7/authentication#retrieving-the-authenticated-user

@telkins
Copy link

telkins commented Oct 19, 2018

@mattkoch614 Also, I just did a quick check to see where the user's set.
It seems that the following method is called: Illuminate\Auth\SessionGuard::setUser(). This is called in a couple of places within Passport, although it's either not called on every Passport request or the user is not valid/available/whatever....not 100% sure.

So, it's a matter of making sure that once a user's available (the OAuth credentials and/or token are validated), then the related user is determined and Illuminate\Auth\SessionGuard::setUser() is called.

It should also be stated that @themsaid seemed to be against this. I still disagree with his assertions for reasons stated by myself and others in this thread, but his input shouldn't be ignored.

I can also see that I backed away from the middleware approach. It may be a fine solution, but it seems like it would be better to not use additional middleware to do any of the things we've been discussing. I do think it's OK to use middleware for Passport-specific routes to authenticate and to set the user as previously discussed, as well as making it easy to provide the current client, token, etc.

@telkins
Copy link

telkins commented Oct 19, 2018

OK. Now I'm even more late....I'm off and will return when I can. ;-)

@Sephster
Copy link
Contributor

Can anyone point to the specific section in the PayPal API where the client grant is used in this way? I'm having trouble finding it as the page linked to is quite large. Would like to see an instance where the client grant has been used in this way as it is not usually recommended.

@Sephster
Copy link
Contributor

Some further questions. Does the user already exist in the protected resource platform? If so, why not use the auth code grant? I am presuming it it because the client grant is being used to create the user but want to check my assumption with this.

@telkins
Copy link

telkins commented Oct 19, 2018

@Sephster I'm not an expert when it comes to PayPal's API(s), but I took a quick look at this page: https://developer.paypal.com/docs/api/overview/

Specifically, I looked at the section on Authentication and Authorization and found the following statement:

A bearer token enables you to complete actions on behalf and with the approval of the resource owner.

In my mind, credentials are often distributed to specific "resource owners"....or users. (This depends on the service, of course. Some services are the same for any/all users. But services like the ones that PayPal exposes via their API(s) seem to be "resource owner" or user-centric.) . I'm not sure how important the terminology is, but if I'm wrong here, then so many other things that follow might also be wrong.

Anyway, a single "resource owner" or user will have access to only their resources. This seems very intuitive and practical to me and doesn't appear to violate any of the OAuth2 documentation that I've read. Looking back at @OscarRPR 's original post, I'm not sure I'm adding anything new to the conversation. :-|

I've only glanced at a few of the API endpoints at the aforementioned page and they all have the same thing in common: none of them seem to apply "globally". A very simple example is creating a webhook (https://developer.paypal.com/docs/api/webhooks/v1/). When doing so, one supplies a URL to be called and a list of event types, which refer to the various events that should trigger the webhook call. It would not make much sense for this to apply "globally"....that is, every time one of these events occurs for any or all resources that your webhook URL is called. So, in this sense, the "context" of the request is the "resource owner" or user.

Does that at least make sense? If not, please help me/us understand what you don't understand.

Does the user already exist in the protected resource platform?

Yes, for the scenarios that I have in mind.

If so, why not use the auth code grant?

Probably for the same reason that PayPal doesn't. Not that it's wrong, it's just choosing the tool/solution that one thinks makes the most sense. For me and what I have been working on, it makes the most sense to have users of my API use the client ID and secret. In these cases, it's much more user friendly and practical (given the usage) to not require "user [to] be asked to login to the authorization server and approve the client." (http://oauth2.thephpleague.com/authorization-server/auth-code-grant/) A website using PayPal's API might have 10s of thousands of users and only one PayPal API account. How would using the auth code grant work in such a case? My API might be used by 100s or 1000s of "clients", each of which has their own sets of users. I don't want to support each of those end-users and my "clients" won't want to have to have them logging in.

But, each app/service/API will have its own set of circumstances that might require a different solution.

Again...make sense?

@telkins
Copy link

telkins commented Oct 19, 2018

@mattkoch614 I took a quick peak at the PR. It looks fine to me. I still think a "more complete" solution will result in a "seamless" experience with regards to the current user. Perhaps that's something for a subsequent PR...? ;-)

@Sephster
Copy link
Contributor

Sephster commented Oct 20, 2018

Thanks for your detailed response @telkins. I'm going to throw in my position on this as I've been following this thread but not really stated my position. I was slow in putting forward my view points here and appreciate this has already been reviewed by @driesvints but wanted to put forwards my thoughts on this matter.

I believe that the client credentials grant should not be associated with a user in the majority of situations. In most of the literature for OAuth 2, it is stated that the client credentials grant should be used for machine to machine or service to service type interactions and not involve the user.

I will try and lay out as best I can why I don't believe this is the correct approach, and why I think this is contravening OAuth 2 accepted conventions (conventions as there are no hard and fast rules on this).

The PayPal Example

In the original posting of this issue, the PayPal API was referenced as an example implementation of using the client grant for a user. From reading through the documents listed, and apologies if I have missed something as the reference page is very long, I don't believe it is being used for the case outlined here.

PayPal is using the client ID to act on behalf of an app (e.g. machine-to-machine):

When you create an app, PayPal generates a set of OAuth client ID and secret credentials for your app for both the sandbox and live environments. You pass these credentials in the Authorization header in a get access token request.

So far so good, and as yet, no support for associating the client grant with a user ID. The next question is, what is PayPal using the client ID to query? From the documentation, it looks like it is using the client credentials grant to associate with a merchant on their store. For example, the PayPal-Auth-Assertion header uses a JWT to identify the merchant:

Specifies an API client-provided JSON Web Token (JWT) assertion that identifies the merchant.

Now you could argue that merchant is a type of user but I don't believe PayPal are using it in this way. I believe they are treating the merchant as a type of microservice, which is again, conventional use for the client ID.

An example that I heard used recently of this is the Trello implementation of OAuth 2. When a user uploads an image or media item to Trello, Trello uses the client credentials grant to authenticate with Amazon S3 and store the image. They don't require and the user cannot give authorization for this use case so this makes sense as again, it is a machine to machine, or microservice to microservice transaction.

So how do actual users interact with PayPal's API? Well it seems the use OpenID connect for this purpose and you can see different pages for this specific purpose here and here.

Again, if we look at the original page referenced, it says the following:

When you create an app, PayPal generates a set of OAuth client ID and secret credentials for your app for both the sandbox and live environments. You pass these credentials in the Authorization header in a get access token request.

How do we register an app? This page details how to and there is an interesting point to raise here. When registering an app, you can mark an option called Login with Paypal.

Log In with PayPal. To enable your users to log in with PayPal, select Log In with PayPal. Then, click Advanced Options and provide information as needed. Log In with PayPal is not currently available for Mobile SDK apps.

From reading the documentation, it seems you can use this option to then add a button into your app which allows the user to authenticate using open connect ID. Again, I don't believe the client credentials grant is being used to represent a user although it isn't clear from the documentation how this interaction happens.

Contrary Claims

I really have tried to find examples where the client grant is used in the manner that has been proposed here but I cannot find them. In most of the OAuth literature this is advocated against:

OAuth.com says the following:

The Client Credentials grant is used when applications request an access token to access their own resources, not on behalf of a user.

Apigee also states that the user should not be used in this scenario:

Most typically, this grant type is used when the app is also the resource owner. For example, an app may need to access a backend cloud-based storage service to store and retrieve data that it uses to perform its work, rather than data specifically owned by the end user. This grant type flow occurs strictly between a client app and the authorization server. An end user does not participate in this grant type flow.

Auth0 also confirms this to be the case:

The Client Credentials Grant (defined in RFC 6749, section 4.4) allows an application to request an Access Token using its Client Id and Client Secret. It is used for non interactive applications (a CLI, a daemon, or a Service running on your backend) where the token is issued to the application itself, instead of an end user.

To counter this, Brent Shaffer's documentation does say this is a use case for the client credentials grant:

calls on behalf of the user who created the client.

But I would err on the side of caution with this use case here as it isn't widely supported from what I can tell and the auth code grant is purpose built for user authorization.

Final Thoughts

I think the sticking point here is that the OAuth spec is fairly nondescript with the use case here:

Client credentials are used as an authorization grant typically when the client is acting on its own behalf (the client is also the resource owner) or is requesting access to protected resources based on an authorization previously arranged with the authorization server.

The second section where it states you can request protected resources based on an authorization previously arranged with the authorization server is fairly vague. People in this thread have used this vagueness to justify representing the user here but as I've noted, it is not the conventional use of the client credentials grant. This is usually interpreted as the whole application or some microservice, but in the vast majority of cases, this is always machine to machine or service to service communication.

I believe that this pull request should probably be reversed but appreciate that I'm trying to shut the barn door after the horse has already bolted and can see why some people are advocating for this change. It could well be the case that I am wrong on my interpretation as the OAuth 2 spec is no prescriptive in a lot of cases but wanted to share my thoughts with everyone here.

@telkins
Copy link

telkins commented Oct 20, 2018

@Sephster Nice response. Lots of good info. I think that you and I might be on the same page...mostly. ;-) I think where we differ is with regards to how we define/use the word "user".

Is a "user" one or more of the following....or something else entirely:

  • a Laravel User instance
  • the end user in whatever application(s) might be being used
  • an account holder
  • whoever/whatever created the associated API client ID
  • a resource owner
  • a (micro)service/app using my API

I think the one that sticks out to me and that I feel is most closely associated with the user you describe in your response is the end user. This is not the same user that I am talking about when I'm talking about users in this context (the context of this issue).

Here are some quotes from your post (with my response at the end base on how I'm reading/understanding it):

When you create an app, PayPal generates a set of OAuth client ID and secret credentials for your app for both the sandbox and live environments. You pass these credentials in the Authorization header in a get access token request. (app = resource owner)

Specifies an API client-provided JSON Web Token (JWT) assertion that identifies the merchant. (merchant = resource owner)

The Client Credentials grant is used when applications request an access token to access their own resources, not on behalf of a user. (applications = resource owner, user = end user)

Most typically, this grant type is used when the app is also the resource owner. For example, an app may need to access a backend cloud-based storage service to store and retrieve data that it uses to perform its work, rather than data specifically owned by the end user. This grant type flow occurs strictly between a client app and the authorization server. An end user does not participate in this grant type flow. (app = resource owner, end user = end user)

The Client Credentials Grant (defined in RFC 6749, section 4.4) allows an application to request an Access Token using its Client Id and Client Secret. It is used for non interactive applications (a CLI, a daemon, or a Service running on your backend) where the token is issued to the application itself, instead of an end user. (application = resource owner, end user = end user)

Client credentials are used as an authorization grant typically when the client is acting on its own behalf (the client is also the resource owner) or is requesting access to protected resources based on an authorization previously arranged with the authorization server. (client = resource owner)

For me, I want to know which Laravel user is associated with the specific client (ID) that is being used in a given API request. Whether or not that's the "end user" is of no importance to me. This "user" for me tells me "whoever/whatever created the associated API client ID" which is the resource owner and inevitably will be some sort of "(micro)service/app using my API".

You also make the following point:

To counter this, Brent Shaffer's documentation does say this is a use case for the client credentials grant: "calls on behalf of the user who created the client."

There is always a "user" who creates the client. This user is the resource owner, as I understand it. It is most often a person, in my experience, but it could also be another app/service/whatever. It might be on behalf of a person, a company, an account, or whatever....but it's always a resource owner. (If I'm wrong here, then I have a lot of re-evauluating and reprocessing to do. ;-) )

The way Laravel and Passport seem to be built up is that there are users (the model instance) and these are (sometimes/normally/always...?) associated with an OAuth2 client. Am I getting things mixed up? Is the individual client the resource owner? Or is the user the resource owner...even if the user has multiple Passport/OAuth2 clients...? Or, is Passport missing something that would better address these kinds of questions and keep people like me from forcing the Eloquent User to be the resource owner...? Or, perhaps finally, should it be the developer who needs to determine the resource owner from the client...?

I'm not 100% sure. I do know that a better use of the word "user" in this discussion is "resource owner". In most of what you described, this resource owner is key. I don't think anyone would think it would be odd to not identify a resource owner for a given request. Whether that resource owner is tied to the client via the out-of-the-box Laravel User or something else....I don't know. But, this is what I want, this is what seems to make sense, and this is what I think most people, including you, would see as reasonable.

That's not an argument for this PR or for tying the client to the Laravel User, but I think it should help us to better understand each other by better defining our terms.

I think I've begun to ramble a bit. I was about to rush out the door, but I wanted to give your response a proper read and hopefully a proper response.

I, too, would have liked a bit more discussion/input before moving forward with this PR, but the PR was a small change and one that can be easily modified/reverted....if necessary.

Anyway, when I get back and have a bit of time, I might take a closer look at the Passport (and underlying OAuth2 code). There is a user parameter and field strewn about the code (as I recall). It's there for a reason and I have to wonder exactly what it is. (Of course, it could be that it shouldn't be there. Who knows...?! ;-) ) But, if anyone else has any input on this part of the overall solution, it might be helpful in shedding a bit more light on this and helping us to make better decisions about what's done with Passport as well as the apps that use it. :-)

@Sephster
Copy link
Contributor

Hey @telkins. Thanks for your response. I think we are talking about the same terms. A resource owner is anyone or thing that can give authorization for a third party to use or access their resources. This could be a user or an app/client or microservice as all of them can have ownership over resources depending on how your API is set up.

It is true, that the OAuth spec isn't specific about whether the resource owner the client is acting on behalf of is an end user or not so one could argue that using the client credentials grant to act on behalf of a user is a legitimate use. However, the conventional wisdom, as I tried to show in my previous post, is that the client credential should not be used for linking to users.

The reason for this is that the client credentials grant is non-interactive. The client credentials grant offers no opportunity in its flow for the end user to authorize the client to have access to its protected resources.

You could argue that if we were to register a client in the backend, and give that client access to all of the end users resources, this would be the same as granting implicit authorisation, but this feels to me like a bit of a hack. I haven't seen the client grant used in this manner and am still struggling to find many concrete examples.

If you have a user account already in your system and want the client to act on your resources, the auth code grant is tailor made for this.

My concern is that this pull request is going against accepted convention, and could lead to people abusing the client credentials grant to avoid the auth code grant which has a few more steps in its flow, but is the correct grant to use in situations such as these.

From the scenario you have laid out, I too would recommend you use the Auth Code Grant instead of the Client Credentials Grant.

@telkins
Copy link

telkins commented Oct 21, 2018

Hi @Sephster . Thanks again for your input. Again, I think we're on the same page...but one or both of us seems to be missing a piece of what the other is saying...I think.

I do wish some third party could jump in here and help me to see what I'm missing or what I'm not stating clearly. Something to help us figure out that last little bit...at least as far as I see it.

Anyway, I'll do my best to try to explain where I think we agree and where I think I'm being misunderstood.

First, you say the following:

However, the conventional wisdom, as I tried to show in my previous post, is that the client credential should not be used for linking to [end] users.

I have modified it to show it in a way that I agree with. You make a good argument in the following statement:

The client credentials grant offers no opportunity in its flow for the end user to authorize the client to have access to its protected resources.

So...again, I'm on the same page. (I'm also getting a better education of OAuth2 in this discussion. Bonus...! ;-) )

From now on I'm going to try to only use the terms resource owner, Eloquent User, and end user.

Here are a collection of somewhat sequential steps/points that might help clarify what I'm wanting/trying to do:

  • The way I see my own API being used is as a kind of SaaS solution.
  • My API offers its service(s) to a resource owner, which I represent in my Laravel application/code as an Eloquent User.
  • When an API request is made, I want to know which resource owner the associated OAuth2 client ID is related to. For me, this is the Eloquent User.
  • From where I sit, I don't care about or manage or have anything to do with the end users. The end users may or may not have any idea that my API is being used.
  • The resource owner has built an app/service that is used by someone else (perhaps end users, perhaps other apps/services).
  • The resource owner's app/service uses the client credentials grant to gain access to their own resources....not the end (or next) user's.
  • I could use the Auth Code Grant, but I think it would complicate my own solution and impose an inconvenient and unnecessary requirement on the end user.
  • A Client Credentials Grant makes sense to me because it's used by the resource owner...aka app/service that is using my API....not on behalf of the end user, but on behalf of itself.

So...there is no implicit authorization. The authorization is explicit and is provided by the resource owner by using its assigned client ID and secret.

Is this not the best way for the scenario I've described? If not, please help me to understand what I should use and why. (I know you recommended using the Auth Code Grant in your last post, but I figured with this (hopefully) new information, you might have a different opinion.)

OK. I guess that's it. I'm very much enjoying this conversation, btw. If nothing else, it's helping me to better understand OAuth2 and whether or not I'm taking the best path for my own solutions.

@driesvints
Copy link
Member

@Sephster @telkins heya. While I really appreciate you both providing very detailed answers it's a little too much for me to digest at the moment. Please try to keep it brief otherwise it's very hard for me to follow along.

The way I see it is as follows: there can be two types of api requests: one by a real user & one by a machine.

The one by a real user can be identified by the auth:api middleware and after that it's available through the Auth::user etc so the api can be protected by whatever you wish (scopes, policies, etc).

The other one, the machine user uses the client credentials way. These api routes are protected by the client middleware. The only problem that exists at the moment is that there's no "easy" way to retrieve the client inside a request.

Now about the second type: it's very important here to not confuse the machine (who makes) the request with the user who created the client. The user who created the client is not the user which makes the requests. You can limit the access resources by retrieving the creator of the client and then use them inside the requests to only return stuff for that user but you cannot "assume" that this is the user making the request. A machine/server makes the request and not the person who created this.

So to overcome this we only need a way to retrieve the current client for the request. For this, I'll try to send in a PR which will solve this in a clean way. @mattkoch614 already sent in an easy way to retrieve the user who created the client: #851. After that it's totally up to you how you use this client (and its creator) to limit requests made to your endpoints.

@driesvints
Copy link
Member

Hey everyone. I've made a PR which hopefully will solve this situation. It basically allows you to retrieve the current used client. After that you're free to use it to limit/authorize your endpoints as you see fit. Because of the PR from @mattkoch614 you also can access the client creator in an easy way.

#854

@driesvints
Copy link
Member

The PR has been merged. I'll send a PR to the docs soon to document the behavior. At least now it should be possible to retrieve the current client and consequently its creator. Then it's up to yourself how you'd use them.

@Sephster
Copy link
Contributor

Sorry @telkins for my delayed response. I understand now what you are saying, and your use case is correct. As you have no end user to authorise the access, and are essentially using machine-to-machine communications, you should be using the client credentials grant as stated.

However, I think where the problem comes in is how you are storing the resource owner. It sounds like you are using Laravel's user table to store this entity. I see why you have done this but I think that linking the client to a user in this manner could lead to people abusing the client credentials grant by using the client credentials grant to link to users instead of using the auth code grant.

I think that what should happen instead, is you should have your client link to some other entity that is custom to your setup, so have a new table called resource_owner or something similar or more appropriate to your setup.

By mixing the concept of user with an end user and machine user, it is opening this grant up for abuse, which is what I have a concern with. @driesvints, now that the client can be retrieved, I would highly recommend that the user retrieval for who created the client is reverted.

@driesvints
Copy link
Member

@Sephster heya. I see your concerns but I believe that forcing people to do anything wouldn't help. The user on the client table is used for all the different client grants so removing the method for the client model would remove it again for all other grants.

I would just leave things be and let people do whatever they want. I know this isn't always wanted but in the end we can't police people on how to implement stuff.

@T0miii
Copy link

T0miii commented Dec 4, 2018

I toke the idea and implemented it to get The User when logging in with a Code Grant, and also using it for some kind of SSO implementation. Here is a Gist if some one needs it:

https://gist.github.com/T0miii/ba63f2154b3a9c03140f5c20daadebec

@yupmin-ct
Copy link

yupmin-ct commented Dec 19, 2018

I used custom token guard for client credentials created by user

auth:api is passed. is it right?

/App/Provider/AppServiceProvider

    public function register()
    {
        $this->app->register(PassportServiceProvider::class);
    }

/App/Passport/PassportServiceProvider

namespace App\Passport;

use Illuminate\Auth\RequestGuard;
use Illuminate\Support\Facades\Auth;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\ResourceServer;
use Laravel\Passport\PassportServiceProvider as BaseServiceProvider;

class PassportServiceProvider extends BaseServiceProvider
{
    /**
     * Make an instance of the token guard.
     *
     * @param  array  $config
     * @return \Illuminate\Auth\RequestGuard
     */
    protected function makeGuard(array $config)
    {
        return new RequestGuard(function ($request) use ($config) {
            return (new TokenGuard(
                $this->app->make(ResourceServer::class),
                Auth::createUserProvider($config['provider']),
                $this->app->make(TokenRepository::class),
                $this->app->make(ClientRepository::class),
                $this->app->make('encrypter')
            ))->user($request);
        }, $this->app['request']);
    }
}

/App/Passport/TokenGuard

namespace App\Passport;

use App\User;
use Laravel\Passport\Guards\TokenGuard as BaseTokenGuard;

class TokenGuard extends BaseTokenGuard
{
    ...
    protected function authenticateViaBearerToken($request)
    {
        ...
        if (! $user) {
            $user = $this->client($request)->user;
            if (! $user instanceof User) {
                return;
            }
        }
       ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests