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

Client Credentials Grant - How Does it Work? #253

Closed
lloy0076 opened this Issue Jan 26, 2017 · 13 comments

Comments

Projects
None yet
6 participants
@lloy0076
Copy link

lloy0076 commented Jan 26, 2017

It's not clear to me that a Passport Server would ever be able to authenticate a client credentials token using the TokenGuard. It seems that tokens passed out for a client_credentials grant type do not contain a subject claim ('sub').

This subject gets translated into what the League's server calls 'oauth_user_id' but that is set to null (because of the no subject issue).

The default checks try to find a user but there is never any user with a null identifier so it should always fail.

The code is a little hairy to track down but:

  1. There's a client repository bridge;
  2. This calls "$record = $this->clients->findActive($clientIdentifier);" on line 39 of passport/src/Bridge/ClientRepository.php;
  3. The method it is in gets called from the TokenGuard which does " $user = $this->provider->retrieveById($psr->getAttribute('oauth_user_id')); if (! $user) { return }" of passport/src/Guards/TokenGuard (lines 115-120).

I used a basic route like this for testing, it was in the API routes (routes/api.php):

Route::middleware('auth:api')->get('/test', function () {
    return response()->json([ 'status' => 'ok' ], 200);  
});                                                      

After successfully retrieving a client_credentials token and passing it, though, I'd always get unauthenticated. By putting a bunch of 'dds' (I know, kludgy but it worked) I found what I've noted above.

It's possible I'm doing something wrong, of course.

System details:

php 7.1.0 run via fastcgid on apache

from composer.lock:

582             "name": "laravel/passport",
 583             "version": "v2.0.0",
 584             "source": {
 585                 "type": "git",
 586                 "url": "https://github.com/laravel/passport.git",
 587                 "reference": "da617aa36720d11ad7f358630715b4ebd585b596"
 588             },

453             "name": "laravel/framework",
 454             "version": "v5.4.0",
 455             "source": {
 456                 "type": "git",
 457                 "url": "https://github.com/laravel/framework.git",
 458                 "reference": "7212b1e9620c36bf806e444f6931cf5f379c68ff"
 459             },
@lloy0076

This comment has been minimized.

Copy link

lloy0076 commented Jan 26, 2017

@RDelorier

This comment has been minimized.

Copy link
Contributor

RDelorier commented Jan 26, 2017

Client credentials are validated with a different middleware since there is no user. https://github.com/laravel/passport/blob/1.0/src/Http/Middleware/CheckClientCredentials.php

@lloy0076

This comment has been minimized.

Copy link

lloy0076 commented Jan 26, 2017

@RDelorier - that makes sense but then it also means it should make sense that the default Middleware SHOULD NOT appear to grant access when that access isn't valid or there's a bug where a clien_credentials grant is being sent to the wrong Middleware during the authentication process (or more likely a misconfiguration somewhere).

@lloy0076

This comment has been minimized.

Copy link

lloy0076 commented Jan 26, 2017

I adjusted the handle method in a local copy of https://github.com/laravel/passport/blob/1.0/src/Http/Middleware/CheckClientCredentials.php to:

 31     /**
 32      * Handle an incoming request.
 33      *
 34      * @param  \Illuminate\Http\Request $request
 35      * @param  \Closure $next
 36      * @return mixed
 37      *
 38      * @throws \Illuminate\Auth\AuthenticationException
 39      */
 40     public function handle($request, Closure $next, ...$scopes)
 41     {
 42         $psr = (new DiactorosFactory)->createRequest($request);
 43         die($psr);
 44
 45         try{
 46             $psr = $this->server->validateAuthenticatedRequest($psr);
 47         } catch (OAuthServerException $e) {
 48             throw new AuthenticationException;
 49         }
 50
 51         foreach ($scopes as $scope) {
 52            if (!in_array($scope,$psr->getAttribute('oauth_scopes'))) {
 53              throw new AuthenticationException;
 54            }
 55          }
 56
 57         return $next($request);
 58     }

(the first two numbers are line numbers)

...and that's not being called.

i.e.

curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "grant_type=client_credentials" -F "client_id=2" -F "client_secret=Eou6kZjdETSQjf2mAlwbSXxlkfAnnkQNkaNCHO61" "http://playground.dev/oauth/token"

That just granted me:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImE4NjFmM2NlNTI1Zjg0NDM0NDJhZTE1ZTY4NzhmMTE0ODY3OGY4NGY2NjYxNTM2MjEzNmMyMGI2OWRlMDQyZjZhMjliY2RmMWE4MzQ2MGI2In0.eyJhdWQiOiIyIiwianRpIjoiYTg2MWYzY2U1MjVmODQ0MzQ0MmFlMTVlNjg3OGYxMTQ4Njc4Zjg0ZjY2NjE1MzYyMTM2YzIwYjY5ZGUwNDJmNmEyOWJjZGYxYTgzNDYwYjYiLCJpYXQiOjE0ODU0NTAzMDgsIm5iZiI6MTQ4NTQ1MDMwOCwiZXhwIjoxNDg2NzQ2MzA4LCJzdWIiOiIiLCJzY29wZXMiOltdfQ.fi3jP5BAvTPXV-c5O36oezibbRd5GwsFpEgNU1uE76zpAsojGpqdb-dDPKA5kMgh75Z3zCLxxA79YKmQEFTCJ_Ablm32f39sNnt7YpJq3ddVGFjyDceXYM2aanwMGEUjAXr1Q9Mt3OXGHju1lPLs7WY7uNJL1HGTAaaWCBFHgUi9DiO-l6A9WUwrlLj914RRqK_b3b7SLfUJ15nO-3uju9QXhtqZ1t51MHxAtqy4fUY1EMM_oJ_03L1wOgg7Soa2hfWOoH6HUp1vuMRvg5NIxZWJeA6V4Zk9MYB-nfTJfpZV13vUYoYeNDnKL7n6d_ROG9Vlu5Rgxb6MGk2Prl4KElbSITn2rgIS9S0I_vvNt1XDf8SHRUBLlKMPLYaBVl7GymAJEbp1nC7N6JTyIIhv3ZJv-b0j-vjLwcS7fcNk7XNCD8xFhK0iUlehXnLFGTnH6ZorKWOiA841snSicJl1fYzDdZBV4ljCPHzOweylcJeplSYSyPrNzqk6RfeWt6r1txpggi0fvKNmmPVF9X9V1pY6Zon5WGzMD-SlXqfCUaGavweK-UUt4CKzQPHXXiHiIexFXxxD__F0r7byszaXOGPaHB8gH4KqU4dlYnqrLMaFrYIsNxWA8qPYOQrGlp0W3f07hj7_g6sGErlCgT4Yf2Pzq1n1MKHx2MSR5-eKVzI
@lloy0076

This comment has been minimized.

Copy link

lloy0076 commented Jan 26, 2017

I added a 'testapi' to my kernel and added a 'routes/testapi' as attached.

I also created a 'slow' middleware group (bad name but I'm just testing) because leaving the bindings in seemed to trigger some error (i.e. I couldn't get the bearer token to work).

curl -X GET -H "Accept: application/json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImZkZGM3M2NhNGMwOWVlNzVhNzY1ZWM4ZWEwMGUwNTc3ZDliYzJlNTM3MDcyZTRmNjQzOGQxM2JiNWQxZjliY2FmZmY4OWJjOWYwYWU2ZWZmIn0.eyJhdWQiOiIyIiwianRpIjoiZmRkYzczY2E0YzA5ZWU3NWE3NjVlYzhlYTAwZTA1NzdkOWJjMmU1MzcwNzJlNGY2NDM4ZDEzYmI1ZDFmOWJjYWZmZjg5YmM5ZjBhZTZlZmYiLCJpYXQiOjE0ODU0NTA1ODQsIm5iZiI6MTQ4NTQ1MDU4NCwiZXhwIjoxNDg2NzQ2NTg0LCJzdWIiOiIiLCJzY29wZXMiOltdfQ.Vvnte_alru9PIArvXNtybXixuNtHMdOhztoWMPKUgEb6iM956r3f4IbHNpcngRGmWKP6wha_NhMlnMetLaU1QumZQBXhuVThX-6W3DQQG2AgspV8vDTc2PGB-23v7PGwsS7_vE5gHJ3t51PKEVCAHHyDfOFKM3jOrDj4tUDNL7ndJI1TBWhqhifnPtu04jn3RzaadvJPfiKWimc5eFYEpOEYevyL0IbJb3bPORSghMkdacuY6k_C88eNRd3z9ld4clI37_-H0SRfj4QFdl_pS5d_OlghcoIa7SFhbNuHJ6i3x5BLQKIhdePeyL72v5idJt8vYZXAsPSO1T7Cx-Kos-LhYPqhWVWMNO13_Oat-cPrljA6k3nTg7T_zBlUzUzMS03eCszMP3g3a5OMBQQ_ROAcHI0STObbEqBNy5ewCRBfGSPyfzWpETfdFHbgwqe_FGLKeuaYFDqCnyORCULA1jzKFE-MeUruX8_1r6fQ_QZfK4BeDTFGqnVGVSY7yoVmo-yyg_5exkWlEn_HBw_PpNXutyy1stfAC9yq_LefbQ2YFjU8eqPWoQ5VnXymCrWu_deN5vWkz9a4Zsc8HVnpIORmN4xSFycfU8kEJMO00bXqpbSfq4q4fS-XLGYRGX7ZLPPUO7akiSdd1X1_106rji3zrhTDSiWnGrdsPvaCvdQ" -H "Cache-Control: no-cache" -H "Postman-Token: 2894f548-8d6a-fc93-2678-1d6306d2e175" "http://playground.dev/testapi/status

app.zip

@RDelorier

This comment has been minimized.

Copy link
Contributor

RDelorier commented Jan 27, 2017

Did you also enable implicit grant?
https://github.com/laravel/passport/blob/1.0/src/Passport.php#L89

Passport::enableImplicitGrant();
@lloy0076

This comment has been minimized.

Copy link

lloy0076 commented Jan 27, 2017

@RDelorier - no, I don't have implicit grant(s) enabled - is that a requirement? If it is, I think I missed where it said it is.

@mapb1990

This comment has been minimized.

Copy link

mapb1990 commented Jan 28, 2017

I'm using passport in lumen 5.4 and I have problems to.
if I use the CheckClientCredentials instead auth middleware I can authenticate the client but not retrieve the authenticated user (Auth::user() return null)
if I use both middlewares all requests return a 401 Unauthorized

@lloy0076

This comment has been minimized.

Copy link

lloy0076 commented Jan 28, 2017

@mapb1990 - The CheckClientCredentials is not meant to return any user.

See:

@georaldc

This comment has been minimized.

Copy link

georaldc commented Feb 27, 2017

@lloy0076 Your die statement won't be called if you are not explicitly using the CheckClientCredentials middleware. You have to define the middleware and apply it to your routes. Lumen example below (should be pretty similar in Laravel, defining middleware in your service provider instead):

bootstrap/app.php

$app->routeMiddleware([
    'auth' => App\Http\Middleware\Authenticate::class,
    'client_credentials' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
]);

routes/web.php

$api->group(['middleware' => ['client_credentials']], function () use ($api) {
    // define client_credentials protected routes
});

I have my own issue though. I was wondering how one could use both auth and this client_credentials middleware depending on the type of token used, so I could use the same route for both.

@palmtale

This comment has been minimized.

Copy link

palmtale commented Mar 5, 2017

@lloy0076 I found the same issue today.
And I found it caused by league/oauth2-server. in League\OAuth2\Server\Grant\ClientCredentialsGrant, Line 38, it set null for user_id, thus no user_id while issueAccessToken, (the same: no user_id store in database table 'oauth_clients').
`class ClientCredentialsGrant extends AbstractGrant
{
/**
* {@inheritdoc}
*/
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
) {
// Validate request
$client = $this->validateClient($request);
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request));

    // Finalize the requested scopes
    $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client);

    // Issue and persist access token
    $accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $scopes);

    // Inject access token into response type
    $responseType->setAccessToken($accessToken);

    return $responseType;
}

/**
 * {@inheritdoc}
 */
public function getIdentifier()
{
    return 'client_credentials';
}

}`

@palmtale

This comment has been minimized.

Copy link

palmtale commented Mar 5, 2017

Then I think this should be update in TokenGuard, after Line 128, if $user == null, find user by clientId

@themsaid

This comment has been minimized.

Copy link
Member

themsaid commented Jul 5, 2017

Closing for lack of activity, hope you got the help you needed :)

@themsaid themsaid closed this Jul 5, 2017

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