-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Customizing Jwt claims and headers needs to be more flexible #199
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
Comments
Glad to see this, @jgrandja ! The |
@joshuatcasey I'm getting close to a flexible design so hoping to have this merged next week. I'll be looking for your teams feedback on whether the 2nd iteration of the design provides the flexibility you need. |
Excellent work @jgrandja, I too am looking forward to this. While we're here, can I raise a couple of issues I stumbled upon today? |
I'm not sure if it will be part of the commit for this ticket. But it will definitely be part of the 0.1.0 (end of month). |
@nickmelis in the meantime it's possible to use the Something like this works for us: @Bean
public JwtEncoder jwtEncoder(final JWKSource jwkSource) {
final NimbusJwsEncoder nimbusJwsEncoder = new NimbusJwsEncoder(jwkSource);
nimbusJwsEncoder.setJwtCustomizer((header, claims) -> claims.issuer("our.url.here"));
return nimbusJwsEncoder;
} |
Oh yes you're absolutely right @joshuatcasey, I didn't even think about it. Thanks! |
@Kehrlann @joshuatcasey @jzheaux @anoopgarlapati @nickmelis I've flushed out the new design for Run the test The updates have only been applied to I likely won't get back to this until Thursday. Feedback would be super helpful to ensure this new design provides the flexibility required for the various use cases. |
I like the flexibility of the model. The One aspect of If we could add the entire Maybe this:
or this:
|
Would it be possible to add something to the context to state the Note: the branch didn't use the customizer for |
This may be a stupid question. As part of my implementation of |
@joshuatcasey I have a major refactoring task for the
Regarding:
The branch only demonstrated for the |
@nickmelis There are no stupid questions :) On the next update, you will no longer have to perform a 2nd DB query as the See previous comment:
|
@jgrandja thanks, really looking forward to it! Do you have an estimate for when this code will go in? |
@joshuatcasey @nickmelis @fhanik @Kehrlann @anoopgarlapati A lot of work was put into the customizer and I think it's pretty flexible. Please take a look at it and provide feedback as soon as you can. I'm releasing Start by looking at these tests:
|
@jgrandja I can confirm it works a treat! I'm really pleased with it! |
Excellent @nickmelis ! Yes, please provide further feedback here if anything else comes up. |
Hi @jgrandja, I noticed that |
@jgrandja I spent some time customizing the OAuth2TokenCustomizer to suit some of the custom claims for my service and it worked great so far. I will let you know of any improvements as I continue to work with it. |
@nickmelis Here is how you would override the @Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
// Override the default signing algorithm from RS256 to ES256
// NOTE: The ES256 key must be available in the configured `JWKSource<SecurityContext>` `@Bean`
context.getHeaders().jwsAlgorithm(SignatureAlgorithm.ES256);
// TODO Further customizations
};
} |
@anoopgarlapati @Kehrlann Thank you for the feedback! I'm happy that it suits your requirements. But I also want to ensure that the API is intuitive to use as this is one of our primary goals. Can you comment on this further? Was it intuitive to use? Does the naming of the API's make sense? e.g. |
private boolean shouldCustomize(JwtEncodingContext context) {
if (!Objects.equals(new TokenType(OidcParameterNames.ID_TOKEN), context.getTokenType())) {
return false;
}
final OAuth2Authorization authorization = context.get(OAuth2Authorization.class);
if (authorization == null) return false;
Set<String> scopes = authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME);
if (scopes == null) return false;
return scopes.contains(OidcScopes.PROFILE);
} |
I'm confused with this statement as I interpret the
If my interpretation is correct then the private boolean shouldCustomize(JwtEncodingContext context) {
if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
OAuth2Authorization authorization = context.getAuthorization();
Set<String> authorizedScopes = authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME);
return authorizedScopes.contains(OidcScopes.PROFILE);
}
return false;
} NOTE: This code is null-safe and if it does throw a NPE then please log an issue as it will be a bug on our end.
The design intent of the |
Right, sorry that was really poorly phrased.
I think what I had in mind was related type-safety, to distinguish between grant types at the When getting the Whereas if you had an This kills the flexibility of Hope that makes more sense! |
@jgrandja In our server we map authorized scopes to a custom claim called @Bean
public OAuth2TokenCustomizer<JwtEncodingContext> customizer() {
return context -> {
if (context.getTokenType() == OAuth2TokenType.ACCESS_TOKEN) {
Set<String> authorizedScopes =
context.getAuthorization().getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME);
if (!CollectionUtils.isEmpty(authorizedScopes)) {
context.getClaims().claims(claims -> {
claims.remove(OAuth2ParameterNames.SCOPE);
claims.put("permissions", authorizedScopes);
});
}
}
};
} When I execute the authorization_code flow with this customization, I do get the access token I desired with This is happening because the scopes in OAuth2AccessToken are set by extracting the I would like to hear thoughts on this suggestion or other alternatives. |
@anoopgarlapati Thanks again for staying on top of the feedback. I totally understand the issue you have. This is a bug and I will fix today. |
@anoopgarlapati This is now resolved via 6ffda38 |
I did consider this hierarchical design, however, it didn't make sense at this point. As I worked through #213, I found the only difference between an
We still need Thanks for pointing this out as I discovered I missed storing the I also added |
@jgrandja Thanks for the updates!! @Bean
public OAuth2TokenCustomizer<JwtEncodingContext> customizer() {
return context -> {
if (context.getTokenType() == OAuth2TokenType.ACCESS_TOKEN) {
OAuth2Authorization oAuth2Authorization = context.getAuthorization();
if (oAuth2Authorization != null) {
OAuth2AuthorizationRequest authorizationRequest =
oAuth2Authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
if (authorizationRequest != null) {
// process custom request parameter and add the custom claim
// similar object for token request is not available in the OAuth2TokenContext for customization
// hence unable to apply this customization for client_credentials and refresh_token grants
}
}
}
};
} The token requests in the three grants ( |
@anoopgarlapati You beat me to it ! :) This is the last change I'm getting in before I release today. The token request (additional) parameters will be stored here. And you can access it via |
@jgrandja makes complete sense! And with the |
@anoopgarlapati See #226 |
Good morning, would anyone have an example that how to use JWT customization(claims) after successful authentication? Thank you so much! |
@giorgimoreira are you looking for a specific code example? There are a few in the tests, if you look for usages of Here's an example that relies on authentication, is this helpful?
|
Can anyone pls help me add some custom parameters to token response?(not inside the claim) |
Hey @atjohn-csam 👋 What you could do is add some The cleanest extension point is probably when those authentication objects are created, but beware, they can be produced in three grant types, each with their own
So you would have to tweak all three of those. Potentially wrap them all in lightweight auth provider: class AddStuffToAccessTokenProvider implements AuthenticationProvider {
private AuthenticationProvider delegate; // this can be either of the above providers
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AccessTokenAuthenticationToken rawAuthentication = delegate.authenticate(authentication);
Map<String, Object> newParameters = new HashMap<>(rawAuthentication.getAdditionalParameters());
newParameters.put(..., ...);
// technically I believe you can mutate the previous auth object...
// but I would advise against it, it might not be possible in the future
return new OAuth2AccessTokenAuthenticationToken(
rawAuthentication.getRegisteredClient(),
rawAuthentication.getPrincipal(),
rawAuthentication.getAccessToken(),
rawAuthentication.getRefreshToken(),
newParameters);
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2ClientCredentialsAuthenticationToken.class.isAssignableFrom(authentication);
}
} Then you have to wire those up, e.g. at init time in the There are other extension point you could hack your way into, such as Hope it helps! [1] I was thinking about |
I can't thank you enough for your help @Kehrlann Kehrlann. I was able to get it work. It was a little tricky to get AddStuffToAccessTokenProvider handle three separate customizations(For the three grant types), but i was too excited to see this output: Thank you again, I hope Spring provides us an easier solution in the next releases. Cheers. |
Hey, no worries! Glad to see it worked for you. FYI I had something like this in mind, but I have not tested it. OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>();
authorizationServerConfigurer
.withObjectPostProcessor(new ObjectPostProcessor<AuthenticationProvider>() {
@Override
public <O extends AuthenticationProvider> O postProcess(O object) {
if (
object instanceof OAuth2AuthorizationCodeAuthenticationProvider
|| object instanceof OAuth2ClientCredentialsAuthenticationProvider
|| object instanceof OAuth2RefreshTokenAuthenticationProvider
) {
return (O) new AddStuffToAccessTokenProvider(object);
} else {
return object;
}
}
}); Cheers! |
Thank you again @Kehrlann Kehrlann. I tried something similar,
The problem was AddStuffToAccessTokenProvider was not able to find the delegate correctly. It always had OAuth2AuthorizationCodeAuthenticationProvider as delegate even though i tried a client_secret workflow. So I ended up creating three separate implantations of AddStuffToAccessTokenProvider, which will get initialized from the bean post processor based on the grant type conditions. Thank you again, your solution works like a charm! |
Do anyone know why the authorities only include scope. We need user details role authorities
|
The
NimbusJwsEncoder.jwtCustomizer
(#173) needs to be re-designed as it does not easily provide all the context required for customization. For example, it is complicated to obtain the associatedOAuth2Authorization
and/orRegisteredClient
in order to provide context for token customization.The text was updated successfully, but these errors were encountered: