thephpleague / oauth2-server

A spec compliant, secure by default PHP OAuth 2.0 Server
https://oauth2.thephpleague.com
MIT License
6.49k stars 1.12k forks source link

client_credentials has empty sub, how to get user information? #1417

Closed BenoitDuffez closed 2 weeks ago

BenoitDuffez commented 2 weeks ago

I am not 100% familiar with OAuth2 so bear with me.

I have an app that has resources and user accounts. I have added this OAuth2 server (with the Symfony bundle) and I want to manage these resources from a smartphone app. I want to have the authentication using OAuth2 so that the smartphone client uses tokens that do not contain the credentials and can be revoked.

I understand that the best grant for this use case is client_credentials. However this grant generates a token with an empty sub:

// league/oauth2-server/src/Grant/ClientCredentialsGrant.php
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes);

The null argument is userIdentifier, and the generated token has ... "sub": "" ....

Once I obtain a token and use it to access a specific resource, the Symfony firewall/bundle will try to load the user using the sub:

// league/oauth2-server-bundle/src/Security/Authenticator/OAuth2Authenticator.php
$userIdentifier = $psr7Request->getAttribute('oauth_user_id', '');

// league/oauth2-server/src/AuthorizationValidators/BearerTokenValidator.php
return $request
    ->withAttribute('oauth_access_token_id', $claims->get('jti'))
    ->withAttribute('oauth_client_id', $this->convertSingleRecordAudToString($claims->get('aud')))
    ->withAttribute('oauth_user_id', $claims->get('sub'))
    ->withAttribute('oauth_scopes', $claims->get('scopes'));

With an empty sub, the userIdentifier is also empty, and thus the DB lookup fails and it returns a dummy NullUser object.

I don't understand why the token generated does not contain a sub (subject?). I've read that this is as per the spec, because the token identifies a machine and not the user. While this is true, the machine acts on the user's behalf, so the server app should be able to retrieve the user from the token.

Now I think I could get the user from the client ID provided in the token, however this looks like Symfony plumbing which is outside of the scope of my question (I guess?)

Thanks

BenoitDuffez commented 2 weeks ago

My understanding was indeed wrong. I now:

Now I'm getting a proper access/bearer token pair and can use private API with the access token.