Closed alexjose closed 2 years ago
Passport can't really set additional (private) claims the way it is now. In fact, Passport makes use of the php league's League\OAuth2\Server\Entities\Traits\AccessTokenTrait
to convert the token to a trait.
/**
* Generate a JWT from the access token
*
* @param CryptKey $privateKey
*
* @return string
*/
public function convertToJWT(CryptKey $privateKey)
{
return (new Builder())
->setAudience($this->getClient()->getIdentifier())
->setId($this->getIdentifier(), true)
->setIssuedAt(time())
->setNotBefore(time())
->setExpiration($this->getExpiryDateTime()->getTimestamp())
->setSubject($this->getUserIdentifier())
->set('scopes', $this->getScopes())
->sign(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase()))
->getToken();
}
You'll notice the ->set('scopes', $this->getScopes())
call, that is how a private claim is set using Lcobucci\JWT\Builder
. So, short of a pull request to override convertToJWT()
on Laravel\Passport\Bridge\AccessToken
with some method of adding custom claims, there is no way currently to do so.
Thank you for the reply @craigpaul.
Is there any plans to do this implementation any soon?
Wouldn't know, you'd have to ask one of the contributors I suppose? @taylorotwell ?
Have the same need. League\OAuth2 provides for extra params (see below) but I don't see how I can override AuthorizationServer::getResponseType since that is in the PassportServiceProvider.
/* * Add custom fields to your Bearer Token response here, then override * AuthorizationServer::getResponseType() to pull in your version of * this class rather than the default. * @param AccessTokenEntityInterface $accessToken * @return array / protected function getExtraParams(AccessTokenEntityInterface $accessToken) { return []; }
Hello everyone, looking into this issue but I'm curious to know what type of custom claims can be attached to a token also how do you normally attach those claims while creating the token?
Thank you
@themsaid Private claims can be anything that two parties agree on using, they are not limited to a predefined set of keys. Read more about the payload here.
Private claims: These are the custom claims created to share information between parties that agree on using them.
As for how they are normally attached, I'm not sure what you mean, if you're talking about how they are represented in the payload, then here is an example.
{
"aud": "1",
"jti": "4ee513a9e858b3b47d09bd5bd522af6bc463fdeb47b6b036b2a2f28ba5b022039f5fe1be61b24aea",
"iat": 1479267759,
"nbf": 1479267759,
"exp": 1480563759,
"sub": "1",
"scopes": [],
// private claims below
"name": "Taylor Otwell",
"admin": true,
}
As for how to attach them to the token, we can either attach parameters to the response or attach custom claims to the JWT as they are two different things. To attach parameters to the response we can override the getExtraParams()
method like @wayne5w mentioned. If the goal is to attach custom claims to the JWT, then overriding the convertToJWT()
method as I've shown above would be the route to take. Personally the convertToJWT()
method makes much more sense as it actually attaches it as a claim on the token.
Hope that answers your question, if not let me know and I'll help with whatever I can.
@craigpaul thank you for the detailed explanation, that answers my questions :)
I believe the desired behaviour here is to attach custom claims to the JWT, I just need some real world example so that I can work on a proposal with a valid use case, do you have any in mind?
@themsaid I've never personally had a use case for this so I have no idea what a valid example would be. Hopefully @alexjose or @wayne5w have an idea of what they were looking for.
In my use case, my app user has resources that can be accessed through devices or APIs. The user then can set which resources would be available to which devices.
Let say the user has 3 resources: A, B, C and 2 devices: 1, 2. Device 1 can access A and B, device 2 can only access C.
I was hoping a token can be created like:
$scopes = [
'get-resources',
];
$claims = [
'resources' => ['A', 'B'],
];
// Pass token to device
$token = $user->createToken('Device 1', $scopes, $claims)->accessToken;
Then I would get the claims from the token and validate them in policies, requests or controllers.
$claims = $user->token()->claims;
I think this feature is really interesting if the token is consumed by a distant resource service, which relies on valid tokens from the authorization service. With custom claims the authorization service can inform the resource service about the user. The resource service can act based on the claims. For instance user roles, but user data could also be provided to create a user record at the resource service.
I would love to use this feature as well when consuming my own API.
I've managed to duplicate this code and test that I can see 'dsl' = 'rules' appears in the resulting JWT.
I'm thinking if we add a:
public function __construct($userIdentifier, array $scopes = [], other_claims = [])
...that simply iterated through those claims we're somewhat there.
IF someone thinks this is a good idea, then I'll fork this and fiddle about.
IF someone would be willing to help me figure out how to get a test rig setup and to make the test to test this (there are tests I'm sure and I wouldn't want to introduce a change without them), I'd be really appreciative.
I think allowing custom claims would really make the access token much more useful. Rather than only being able to authenticate requests to the api it would allow us to display additional user information without pounding the api for the same simple information over and over.
My user interface is a single page application which is build and served from a cdn. All authentication is handled by my auth service which is running on passport.
Each time the page loads I want to show the current users name and avatar in the header, as well as limit some ui elements based on the roles of the user. Currently I make an ajax call with the token to fetch the user data. With custom claims that ajax call would go away and I could just use something like jwt-decode to get the information I need from the access token itself.
In addition to the auth service and the front end I have multiple resource servers that must all authorize requests using the auth service as the source of truth.
When a user wants to update a restricted resource I must verify the user has the correct permission to do so. This means I need to make a request to the auth service to retrieve the users permissions before I can proceed. If the permissions were available on the access token then I could use the public key from my auth service to verify and decode the access token then I would have all the information I needed to continue.
When users connect to my site I want to connect them to a web socket once they are authenticated, I also want to add their name to a list of online users. Given only the access token I need to again requests the users basic info (name, avatar) from my auth service. If this information were on the access token that would not be necessary. Just like the resource server my socket server could use my auth services public key to validate and decode the token giving me all the information necessary.
@themsaid I hope these use cases are helpful. I'm currently accomplishing this by registering a custom AccessTokenRepository but it would be nice If someone could find a clean way to implement this as a core feature. Here is a gist of my current setup in case it helps https://gist.github.com/RDelorier/9ec45bbb595b7e21c30df80c34b03cac
@themsaid It's been 8 months since this was raised. Are there any plans on supporting this? I would prefer a native passport jwt solution versus going with another jwt package.
If you are not too concerned with stuffing the extra parameters inside the token, we have this brittle workaround:
<?php
namespace App;
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
class UserMetaBearerTokenResponse extends BearerTokenResponse
{
protected function getExtraParams(AccessTokenEntityInterface $accessToken)
{
$user = User::find($this->accessToken->getUserIdentifier());
$meta = $user->toArray();
$meta['club_id'] = 'whatever';
return $meta;
}
}
Then this:
<?php
/**
* --------------------------------------------------------------------------
* NOTE:
* --------------------------------------------------------------------------
* We are using this override to provide custom response fields that gets
* sent with the token when a user is authenticated
*
*/
namespace App\Providers;
use League\OAuth2\Server\AuthorizationServer;
class PassportServiceProvider extends \Laravel\Passport\PassportServiceProvider
{
/**
* Make the authorization service instance.
* This should keep in step with upgrades to the parent (yikes!):
*
* @link https://github.com/laravel/passport/blob/master/src/PassportServiceProvider.php#L196
*
* @return League\OAuth2\Server\AuthorizationServer
*/
public function makeAuthorizationServer()
{
return new AuthorizationServer(
$this->app->make(\Laravel\Passport\Bridge\ClientRepository::class),
$this->app->make(\Laravel\Passport\Bridge\AccessTokenRepository::class),
$this->app->make(\Laravel\Passport\Bridge\ScopeRepository::class),
$this->makeCryptKey('oauth-private.key'),
app('encrypter')->getKey(),
$this->app->make(\App\UserMetaBearerTokenResponse::class) // we add this line
);
}
}
Jumping on the bandwagon because I also have a need for custom claims. I'm working on a multi-tenant SaaS application and it would be great if I could store the user's group ID in the token. Especially because my users can belong to multiple groups, so checking which group they currently have access to has been a major challenge so far.
Custom claims still haven't been added. At this moment, I have to make a request for a token and then make a request to get my users information - this hurts my feelings.
looking for the same feature here... :-(
Need this too.
I needed this too. I don't know whether my way is recommended but I did the following.
<?php
namespace App\Auth\Model;
use Laravel\Passport\Bridge\AccessToken as PassportAccessToken; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Rsa\Sha256; use League\OAuth2\Server\CryptKey;
class AccessToken extends PassportAccessToken { public function convertToJWT(CryptKey $privateKey) { return (new Builder()) ->setAudience($this->getClient()->getIdentifier()) ->setId($this->getIdentifier(), true) ->setIssuedAt(time()) ->setNotBefore(time()) ->setExpiration($this->getExpiryDateTime()->getTimestamp()) ->setSubject($this->getUserIdentifier()) ->set('scopes', $this->getScopes()) ->set('roles', $this->getRoles()) // my custom claims ->sign(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase())) ->getToken(); }
// my custom claims for roles // Just an example. public function getRoles() { return [ 'admin', 'super admin' ]; } }
2. Created an AccessTokenRepository class _(overrides getNewToken)_
<?php namespace App\Auth\Repository;
use App\Auth\Model\AccessToken; // AccessToken from step 1 use League\OAuth2\Server\Entities\ClientEntityInterface; use Laravel\Passport\Bridge\AccessTokenRepository as PassportAccessTokenRepository;
class AccessTokenRepository extends PassportAccessTokenRepository { public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null) { return new AccessToken($userIdentifier, $scopes); // AccessToken from step 1 } }
3. Created a PassportServiceProvider class _(overrides makeAuthorizationServer)_
<?php
namespace App\Auth\Providers;
use League\OAuth2\Server\AuthorizationServer;
class PassportServiceProvider extends \Laravel\Passport\PassportServiceProvider { public function makeAuthorizationServer() { return new AuthorizationServer( $this->app->make(\Laravel\Passport\Bridge\ClientRepository::class), $this->app->make(\App\Auth\Repository\AccessTokenRepository::class), // AccessTokenRepository from step 2 $this->app->make(\Laravel\Passport\Bridge\ScopeRepository::class), $this->makeCryptKey('oauth-private.key'), app('encrypter')->getKey() ); }
}
4. Updated config/app.php
'providers' => [ ... App\Auth\Providers\PassportServiceProvider::class // PassportServiceProvider from step 3 ],
The resulting JWT created should be something like below:
{ "aud": "2", "jti": "844d6e40f67aff3082db669b54ac5e02d0307f98d7724e5db266faa94fcaff9712570847c782978a", "iat": 1521428436, "nbf": 1521428436, "exp": 1552964436, "sub": "1", "scopes": [], "roles": [ "admin", "super admin" ] }
Claims support is a fairly essential part of any stateless identity framework. Would love to know when this is going to be implemented.
Basic gist here would be that some time after successful authentication, we need a place to hook code that returns a dictionary of claims.
As I mention in the issue I link below, my use case is including more than the user's ID in the token. Specifically the tenant the token applies to as well as the current organization.
In 3rd step, there is use App\Auth\TokenServer;
What is it?
@yavuzkoca you can remove that line.
@JannieT Your answer worked for me but i suggest a change; pass only 'private' keyword rather than 'oauth-private.key' to 'makeCryptKey' function.
@driesvints I had a bit of a look at this, and there's a quick, and probably a long term option. But they're not mutually exclusive so it would be an option imo to implement the quick option even in 7.0 because it would be fully backwards compatible.
It's obvious from the league code that the way to handle custom claims is to simply insert your own class that implements a ResponseTypeInterface (https://github.com/thephpleague/oauth2-server/blob/master/src/AuthorizationServer.php#L110). This because their default BearerTokenResponse adds only a scope claim. There simply isn't a way to just add your own claims.
So laravel could solve this in 2 ways I think.
Option 1 is actually pretty easy as everything is already there to support it and then at least people don't have to override all kinds of laravel classes. It would basically be this patch: https://github.com/corbosman/passport/commit/6af946bc5f06f099831458235b3beb6a07eddcad
This would allow people to do something like:
Passport::setResponseType(new CustomBearerTokenResponse);
With a class like:
<?php
namespace App\Http\Responses;
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
class CustomBearerTokenResponse extends BearerTokenResponse implements ResponseTypeInterface
{
protected function getExtraParams(AccessTokenEntityInterface $accessToken)
{
return [
'name' => 'foo'
];
}
}
In this class I only override the getExtraParams method, but one could also override the generateHttpResponse method and create their own response including any claims. This works, and results in an accessToken like:
League\OAuth2\Client\Token\AccessToken {#507
#accessToken: "...."
#expires: 1573634572
#refreshToken: "..."
#resourceOwnerId: null
#values: array:2 [
"name" => "foo"
"token_type" => "Bearer"
]
}
I think this would solve the immediate issue in this ticket, and laravel could always opt in the future to add its own BearerToken with more configurability, but personally I don't think that's even needed. The league server doesn't support it either.
Comments?
I think it would be more appropriate to add this feature to the League's OAuth Server. If it is added in Passport, there is a chance that in future, it will conflict with the server's actual implementation.
There is already a PR open for this problem https://github.com/thephpleague/oauth2-server/pull/924 - Just need to get some time to finish reviewing it
Are you talking option 1 or 2? Because option 1, simply inserting your own implementation of a ResponseTypeInterface would probably be still supported in the future in the league oauth server right? I totally agree that laravel should probably not do anything more elaborate than simply passing a ResponseTypeInterface implementation in the AuthorizationServer constructor. I said as much. Or are you saying the idea is to drop that from the constructor?
My suggestion would at least give people the option to implement this without having to override laravel classes. And it wouldn't break anything because the default would be null. And if the league oauth2 server does have better support for configuring the response in the future, even better, but that still wouldn't conflict with simply adding your own implementation as per this suggested patch?
I'm talking about both options. Implementing this in Passport is possible. As you state, the interface for ResponseTypeInterface
is unlikely to change, but adding this feature into Passport when it is already planned for the OAuth2-Server seems unnecessary. It could force Passport to be in conflict with future changes in the server and make maintainability more difficult.
In addition, when the custom claims are added to the OAuth2-Server, you then have the problem of either transitioning to it (potentially requiring a major change) or maintaining a custom solution indefinitely adding more work to the Passport maintainers
These concerns might not come to pass but I don't know and I suppose that is what my concern is. All of these issues or potential pitfalls go away if the feature is implemented in the OAuth2-Server and hooked into from Passport.
Think it's probably best to wait for OAuth2-Server to implement it first like @Sephster suggested.
I guess I dont understand the PR that @Sephster referenced. It still seems to imply that the way to add claims to a token is to override the BearerTokenResponse. The comment in that PR states:
TLDR: you can now add extra information to:
* the access token through the convertToJWT method in your class implementing the AccessTokenEntityInterface interface.
* the refresh token by creating your own instance of the BearerTokenResponse class and overwriting generateHttpResponse
* the auth code by creating your own instance of the AuthCodeGrant class and overwriting the two empty methods for adding your own parameters
* the token endpoint response by creating your own instance of the BearerTokenResponse class and overwriting getExtraParams.
This seems to imply you still need to provide your own implementation of a ResponseTypeInterface. But right now, Laravel wont let you, and my patch simply adds the option to allow Laravel to let the end user provide their own implementation of the ResponseTypeInterface , so they can actually implement the methods mentioned above.
Doesn't it actually complement that PR? Laravel still needs to add the option for an end user to provide their own BearerTokenResponse. Sure, after that PR gets merged end users will have even more options, but I guess I dont see where they would conflict with what im suggesting in option 1 (forget about option 2).
What @corbosman's proposed is already implemented? It'd be awesome...
Any news about this issue? Is there any workaround to customize JWT claims in Laravel 5.8 / Passport latest version access tokens?
Thank you
@bigyanshr in what files are you storing the code for steps 1, 2 and 3?
i am getting Class App\Providers\AccessTokenRepository does not exist
error.
Thanks!
Any news about this issue?
@bigyanshr Many functions are deprecated. I made the changes to the 1 step.
<?php
namespace App\Auth;
use Laravel\Passport\Bridge\AccessToken as PassportAccessToken; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Rsa\Sha256; use League\OAuth2\Server\CryptKey; use App\Models\User; use App\Http\Resources\Auth\Login as LoginResources;
class AccessToken extends PassportAccessToken { public function convertToJWT(CryptKey $privateKey) { $builder = new Builder(); $builder->permittedFor($this->getClient()->getIdentifier()) ->identifiedBy($this->getIdentifier(), true) ->issuedAt(time()) ->canOnlyBeUsedAfter(time()) ->expiresAt($this->getExpiryDateTime()->getTimestamp()) ->relatedTo($this->getUserIdentifier()) ->withClaim('scopes', $this->getScopes()); if ($user = User::find($this->getUserIdentifier())) { $builder ->withClaim('user', new LoginResources($user)); } return $builder ->getToken(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase())); }
}
Any updates?
It looks like the issue in oauth2-server was closed by the author, and I dont see any obvious way in the latest oauth2-server how an application like laravel could add claims to the bearer token. Anyone look into this?
@corbosman You'd need to follow the process set out by @bigyanshr above, whereby you extend the AccessToken and AccessTokenRepository classes and override their bindings in an extended PassportServiceProvider (also perhaps disabling auto-discovery of the laravel/passport service provider to prevent clash).
It's a bit hacky, but unfortunately looking at how long this issue and its discussion has been ongoing, don't hold your breath for an officially supported approach.
@uphlewis sure, ive been doing that for ages. It's not too bad really. But the whole idea of this ticket was to not have to override all kinds of classes. I provided a possible solution here https://github.com/laravel/passport/issues/94#issuecomment-438190183 , but Sephster said laravel should just wait until the league server implemented some changes, referring to a specific ticket. But that ticket was closed, without any of the necessary work done to allow setting claims, as far as i can tell.
So we're back to square one. How can we let laravel passport developers add custom claims to the token without a bunch of overrides. I still think the only way, which is totally backwards compatible in passport, it wouldnt break a thing, is to allow developers to inject their own ResponseTypeInterface implementation.
Any news on this issue? Are we still sticking to these quickfixes?
@neverovski @bigyanshr had to update with passport 8 / oauth2-server 8 to override the __toString()
method instead.
<?php
namespace App\Passport;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use League\OAuth2\Server\CryptKey;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Laravel\Passport\Bridge\AccessToken as PassportAccessToken;
class AccessToken extends PassportAccessToken
{
private $privateKey;
/**
* Generate a string representation from the access token
*/
public function __toString()
{
return (string) $this->convertToJWT($this->privateKey);
}
/**
* Set the private key used to encrypt this access token.
*/
public function setPrivateKey(CryptKey $privateKey)
{
$this->privateKey = $privateKey;
}
...
here is an updated gist using Passport 8 with Laravel 6 to include custom claims
https://gist.github.com/onamfc/0422da15743918e653888441ba6226ca
@onamfc It works well for me in Laravel 6. thank you all.
if this ever does get implemented, it would be outstanding to have a way to check claims as easily as you can check scopes via middleware, helper methods etc. I got really far with @onamfc's gist (thank you so much) but needing to check the claim values in middleware and still wanting to use Passport::actingAs
in my tests made me fallback to scopes.
Might be for the best? But I would love to have the option of doing one or both.... my experience with past oauth/jwt implementations have not made this difficult at all, though obviously the advantages of laravel as a whole outweigh this particular concern.
in the future i may abandon scopes for this and add in the custom claim stuff above and supply my own token model to include those claims in the DB somewhere so I can build a middleware around it that checks for a combination of feature set the user has access to and scope granted to a token? I hate that I can't just get at the claims on the token (unless i can and i don't know how). as of now the scopes drive the whole thing in what feels like a very unnatural way
We've been extending Passport to add custom claims for a while now. We currently use a method similar to the gist a few posts up. Today I had a look if it would be possible to extract claims support into a package. I made a quick proof-of-concept. It seems to work in our Passport setup, but feel free to play with it and maybe report back on the package issue tracker. I'd love to hear people's ideas about it. This is for the current version of Passport, it likely won't work for previous versions yet.
We are currently running the above package in production, and I know of 2 others that do as well, so I guess it's all working fine. I'll keep it updated as newer versions of Passport appear and this issue hasn't been resolved in Laravel Passport itself in some way.
We are currently running the above package in production, and I know of 2 others that do as well, so I guess it's all working fine. I'll keep it updated as newer versions of Passport appear and this issue hasn't been resolved in Laravel Passport itself in some way.
I did an article for who that want to do this and don't know how to. Overloading some classes (less as I can). I hope this help others in the future The post about it
I have done and tested it with Laravel 6 y Passport 7.5
@onamfc It worked in Laravel 5.8. Thanks! <3
We are currently running the above package in production, and I know of 2 others that do as well, so I guess it's all working fine. I'll keep it updated as newer versions of Passport appear and this issue hasn't been resolved in Laravel Passport itself in some way.
Tested it with laravel 7.10.x and laravel passport 8.5.0 and it is working. Great job. Thanks a lot :) However, this should be supported by default!
Hi,
I want to add some user info into my JWT. It can be done by changing the response type of the authorization server with bearer token supporting extra params.
Ref: https://github.com/thephpleague/oauth2-server/blob/master/tests/ResponseTypes/BearerTokenResponseWithParams.php
But I am not sure how can I change the response type inside the passport.