I know that the Grant-Type client_credentials isn't in the documentation, but it is explicitly enabled in the ServiceProvider https://github.com/laravel/passport/blob/4033d183d6d66ea5d1206060e2211dd42c5b4e88/src/PassportServiceProvider.php#L104-L106
Requesting a access token works, but after that it always says unauthorized. I think it is because the user_id isn't set in the table 'oauth_access_tokens'.
Is the bug that 'client_credentials' is enabled or that you can't authenticate afterwards? Are there plans to support it?
Client credentials was added here: #34
It's possibly failing because it can't find a user to associate with the token.
You can add to the routeMiddleware in \App\Http\Kernel.php
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
//add new line for client credentials
'client_credentials' => \App\Http\Middleware\CheckClientCredentials::class
]
then check on your routes with:
Route::get('test-credentials', ['middleware' => 'client_credentials','uses'=>'ExampleController@index']);
The client credentials grant is set without a user identifier as you can see in the respondToAccessTokenRequest() method in League\Oauth2\Server\Grant\ClientCredentialsGrant.
...
// Issue and persist access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $scopes);
...
That null param is usually the user identifier which is used to set the sub claim on the JWT. Since it is null our JWT comes back with a sub claim of "". So now when we try to authenticateViaBearerToken(), we will return no user, as you can see in the following lines.
...
// If the access token is valid we will retrieve the user according to the user ID
// associated with the token. We will use the provider implementation which may
// be used to retrieve users from Eloquent. Next, we'll be ready to continue.
$user = $this->provider->retrieveById(
$psr->getAttribute('oauth_user_id')
);
if (! $user) {
return;
}
...
So now, since we return no user from that, we can go all the way back up to the auth:api middleware. It goes through each of the guards provided, which in this case is just api (or the Laravel\Passport\Guards\TokenGuard in the case of using Passport. Inside the looping of the guards we see this.
...
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
...
We never pass the ->check() method successfully, and thus we throw a new AuthenticationException. That would be why you're always getting unauthenticated after you get your token.
The way I see it, its either a bug, or the grant is being used improperly. Given there is no documentation yet, that's probably a strong possibility, but we should still get @themsaid's opinion.
We still need to document this, and yes this type of grants provides no user instance so the normal Passport guard won't work, the only way to use this grant is by using a custom middleware made for this purpose.
Thanks @ganey
It worked with using the CheckClientCredentials Middleware.
Personally I'm happy with the solution, this way I can distinguish between user based apis and first class api.
Also thanks @craigpaul and @themsaid for further clarification
Personally I'm happy with the solution, this way I can distinguish between user based apis and first class api.
Well, that's the point yeah :)
Glad you're happy.
How do i use this middle ware, whenever I try to use it i get "Class App\Http\Middleware\CheckClientCredentials does not exist", please help?
@prashanth27101989 The middleware isn't defined in your app's http namespace, this is the correct namespace.
Laravel\Passport\Http\Middleware\CheckClientCredentials
OH god, sorry, my stupidity, should have searched for that class in passport package, it worked, awesome :)
I seem to get Auth guard [client_credentials] is not defined. Whenever i try and use the CheckClientCredentials middleware
@craigpaul works!!
any workaround if I want to get the user who owns the access token?
@craigpaul can you show how to "define in my app's http namespace" please. I'm new to this and not sure of all the steps to take to get CheckClientCredentials to work properly.
I'm assuming thats why I'm having issues.. However, I do not get "Unauthorized" when I send a request using Postman and hit the following Route in my api.php file, it returns my login page.
Route::get('/user', function(Request $request) {
return $request->user();
})->middleware('allScopes:access-subscription');
If I remove ->middleware('allScopes:access-subscription') then it returns nothing. Not sure what I'm doing wrong.
@ampedpublishing All that means is that @prashanth27101989 was trying to use the incorrect FQCN (fully qualified class name) when adding the middleware to the $routeMiddleware property. They were using this...
protected $routeMiddleware = [
...
\App\Http\Middleware\CheckClientCredentials::class,
]
When they should have been doing this...
protected $routeMiddleware = [
...
\Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
];
The reason is because that file is located in the Passport vendor folder that would get installed when doing a composer install. It does not exist inside your app/Http/Middleware folder like the original FQCN would have suggested. Hope that explains it well enough.
@craigpaul got it, thanks
@craigpaul I added the the correct FQCN as you pointed out, but no matter what I do Postman always returns the login page. I have tried both of these Routes:
Route::get('/user', function(Request $request) {
return $request->user();
})->middleware('client_credentials');
AND...
Route::get('/getKey', ['middleware' => 'client_credentials','uses'=>'LicenseKeyController@getKey']);
Any suggestions?
@ampedpublishing Most likely, the thing that would cause that is the try catch block in the handle method of CheckClientCredentials. I'd suggest taking a look there and perhaps make a post on Laracasts with some more detail, you'll get feedback a lot quicker there then here.
@ampedpublishing You should add the header "Accept: application/json" in Postman, it should now return "{"error":0,"message":"Unauthenticated"}
What if I have routes that work depending on user context, how does one go about that? For example, I would like to have a GET listing resource route that works like the following:
Do I have to do something like create a custom middleware solution that uses both the Passport's TokenGuard guard and CheckClientCredentials middleware, depending on how the token looks like? Or is this api approach wrong in the first place?
@georaldc I have a similar endpoint /oauth/me which returns either the user or the client associated with the current token. I simply check if there's a user logged in (auth()->user()) and return it or return the client if not.
So basically, if authentication happens via client credentials, there will be no user. If auth is made via the password grant, there will be a user.
Was having problems with this too.
Using the aforementioned \Laravel\Passport\Http\Middleware\CheckClientCredentials::class middleware solved the problem.
This should really be documented to save other people the frustrating / lost time that I just went though.
Is there a reason why the CheckClientCredentials middleware isn't documented? Seems a little strange
Muchas gracias, estaba presentando el mismo problema. Pasaba el token en la cabecera de Autorización pero me devolvía un código de estado 401, pero con el Middleware CheckClientCredentials lo pude solucionar, gracias de nuevo.
I lost a whole day of banging my head against the wall until I found this thread. Why is this not mentioned in the docs????
if Unauthenticated ,it's return 500 is right? i think it should return 401
Most helpful comment
Was having problems with this too.
Using the aforementioned
\Laravel\Passport\Http\Middleware\CheckClientCredentials::classmiddleware solved the problem.This should really be documented to save other people the frustrating / lost time that I just went though.