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

v9 Client Credentials grant broken #1456

Open
shades684 opened this issue Oct 23, 2024 · 9 comments
Open

v9 Client Credentials grant broken #1456

shades684 opened this issue Oct 23, 2024 · 9 comments

Comments

@shades684
Copy link

Since version 9 the client credentials grant fills the subject of the jwt token with the client identifier
On line 56 of League\OAuth2\Server\Grant\ClientCredentialsGrant the userIdentifier is set to null
On line 107 of League\OAuth2\Server\Entities\Traits\AccessTokenTrait the SubjectIdentifier is therefor filled with de identifier of the client

When using the given jwt token:
On line 132 of League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator the oauth_user_id is set to the value of sub (which is the subject identifier mentioned above)

The definition of sub says:

sub (subject): Subject of the JWT (the user)

This value if used in
Line 91 of League\Bundle\OAuth2ServerBundle\Security\Authenticator checks if sub should return a NullUser if we don't have a user, but now it doesn't and we get an error.

I expect the SubjectIdentifier of the AccessTokenTrait should be nullable and not return the client id in the subject if we have no user.
I don't know if this has been done intentionally or my assumptions are correct.

@hafezdivandari
Copy link
Contributor

Using "client credentials" grant, the client is the resource owner, so is the subject of the JWT token.

@shades684
Copy link
Author

I can agree with that, but that doesn't make it the user, so maybe the oauth_user_id shouldn't be filled with the sub if the sub is filled with the client id?

@ajgarlag
Copy link

@shades684 Is this issue related to thephpleague/oauth2-server-bundle#191?

@hafezdivandari
Copy link
Contributor

I can agree with that, but that doesn't make it the user, so maybe the oauth_user_id shouldn't be filled with the sub if the sub is filled with the client id?

I understand what you mean and it is a naming problem IMO. The oauth_user_id could be oauth_owner_id maybe, or adding a oauth_owner_type with 'client'|'user' may help. But the user is the one who use the token to access the authenticated resource, so the client is the resource owner and also is the user!

We also rely on the new behavior of v9 on Laravel Passport to check whether the token's resource owner is the client itself or not.

You may check the situation too like we did and set oauth_user_id to null?

if ($request->oauth_user_id === $request->oauth_client_id) {
    $request->oauth_user_id = null;
}

@shades684
Copy link
Author

shades684 commented Oct 24, 2024

@ajgarlag bartholtbos explains the same problem in that issue, but I think he's searching for the solution in the wrong project.
This project fills the oauth_user_id with the client identifier of the sub. But the identifier in the sub is not a user identifier.

@hafezdivandari though i think your solution will work, it feels like we're testing if 2 ids from seperate places are the same. Theoretically they could actually be the same id.

@hafezdivandari
An option might be to add a custom field to the jwt describing what the sub contains? I really don't see a better way to set the oauth_user_id to null unless the sub is left to null when no user identifier exists.

@hafezdivandari
Copy link
Contributor

hafezdivandari commented Oct 24, 2024

though i think your solution will work, it feels like we're testing if 2 ids from seperate places are the same. Theoretically they could actually be the same id.

@shades684 you are right, this can't be used if client and user IDs are integers for example. We are using UUID for client IDs on Passport so we are safe. But your point is totally valid.

An option might be to add a custom field to the jwt describing what the sub contains? I really don't see a better way to set the oauth_user_id to null unless the sub is left to null when no user identifier exists.

I agree. It seems wrong to map sub into oauth_user_id without having additional describing attribute!

@hafezdivandari
Copy link
Contributor

hafezdivandari commented Oct 24, 2024

Ok, I finally found the related RFC9068 which is too important to know what the sub should be:

sub REQUIRED - as defined in Section 4.1.2 of [RFC7519]. In cases of access tokens obtained through grants where a resource owner is involved, such as the authorization code grant, the value of "sub" SHOULD correspond to the subject identifier of the resource owner. In cases of access tokens obtained through grants where no resource owner is involved, such as the client credentials grant, the value of "sub" SHOULD correspond to an identifier the authorization server uses to indicate the client application. See Section 5 for more details on this scenario. Also, see Section 6 for a discussion about how different choices in assigning "sub" values can impact privacy.

@Sephster
Copy link
Member

This is a tricky one to resolve. It's correct that the subject should be filled with the client ID when using the client credentials grant as noted by @hafezdivandari.

The problem is populating the oauth_user_id field with it when it is clearly not a user ID as it could cause confusion with implementations.

I'm not entirely sure what the best way forwards is with this. We could rename the request field to oauth_owner_id or something similar in a later release but if we did this, it would then be correct to assign the sub to this field and I assume you'd still have issues as you want to know when no resource owner is present? Am I understanding that correctly?

My gut is telling me we change the name of this in a later release to satisfy when the sub switches between a client ID or resource owner ID and that a modified trait is used in the League Bundle to reinstate the old behaviour for Symfony users in the meantime.

@hafezdivandari
Copy link
Contributor

hafezdivandari commented Dec 3, 2024

Adding a custom claim sub_type may help:

// src/Entities/Traits/AccessTokenTrait.php

 private function convertToJWT(): Token
 {
     // ...

     return $this->jwtConfiguration->builder()
         // ...
         ->relatedTo($this->getSubjectIdentifier())
+        ->withClaim('sub_type', $this->getSubjectType())
         ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey());
 }

+ private function getSubjectType(): string
+ {
+     return is_null($this->getUserIdentifier()) ? 'client' : 'user';
+ }

 private function getSubjectIdentifier(): string
 {
     return $this->getUserIdentifier() ?? $this->getClient()->getIdentifier();
 }
// src/AuthorizationValidators/BearerTokenValidator.php

 public function validateAuthorization(ServerRequestInterface $request): ServerRequestInterface
 {
     // ...

     return $request
         ->withAttribute('oauth_access_token_id', $claims->get('jti'))
         ->withAttribute('oauth_client_id', $claims->get('aud')[0])
-        ->withAttribute('oauth_user_id', $claims->get('sub'))
+        ->withAttribute('oauth_user_id', $claims->get('sub_type') === 'user' ? $claims->get('sub') : null)
+        ->withAttribute('oauth_owner_id', $claims->get('sub'))
+        ->withAttribute('oauth_owner_type', $claims->get('sub_type'))
         ->withAttribute('oauth_scopes', $claims->get('scopes'));
 }

PS: Adding custom claims would be easier after #1328

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

No branches or pull requests

4 participants