Today I'm using a custom grant type password, I've implemented token generation, but I can't place custom claims, such as authorities and role
@RequiredArgsConstructor
@Slf4j
public class CustomPasswordGrantAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private final AuthenticationManager authenticationManager;
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
@Autowired
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
CustomPasswordGrantAuthenticationToken passwordGrantAuthentication =
(CustomPasswordGrantAuthenticationToken) authentication;
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(authentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
if (registeredClient == null) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
// Ensure the client is configured to use this authorization grant type
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
Authentication usernamePasswordAuthentication = getUsernamePasswordAuthentication(passwordGrantAuthentication);
// Generate the access token
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(usernamePasswordAuthentication)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.authorizationGrant(passwordGrantAuthentication);
// final var account = (AccountDetails) userDetailsService.loadUserByUsername("teste@teste.com");
//
// Map<String, Object> customClaims = new HashMap<>();
// customClaims.put("role", account.getRole());
// customClaims.put("authorities", account.getAuthorities());
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
if (generatedAccessToken == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the access token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), null);
// Initialize the OAuth2Authorization
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(usernamePasswordAuthentication.getName())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
metadata.put(
OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
((ClaimAccessor) generatedAccessToken).getClaims()));
} else {
authorizationBuilder.accessToken(accessToken);
}
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
authorizationBuilder.refreshToken(refreshToken);
}
OAuth2Authorization authorization = authorizationBuilder.build();
// Save the OAuth2Authorization
this.authorizationService.save(authorization);
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken);
}
@Override
public boolean supports(Class<?> authentication) {
return CustomPasswordGrantAuthenticationToken.class.isAssignableFrom(authentication);
}
private Authentication getUsernamePasswordAuthentication(CustomPasswordGrantAuthenticationToken resouceOwnerPasswordAuthentication) {
Map<String, Object> additionalParameters = resouceOwnerPasswordAuthentication.getAdditionalParameters();
String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);
String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
log.info("got usernamePasswordAuthenticationToken=" + usernamePasswordAuthenticationToken);
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
return usernamePasswordAuthentication;
}
private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
OAuth2ClientAuthenticationToken clientPrincipal = null;
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
}
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
return clientPrincipal;
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
}
My token generator and token customizer:
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return context -> {
Authentication principal = context.getPrincipal();
if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
AccountDetails accountDetails = (AccountDetails) principal.getPrincipal();
context.getClaims().claim("role", accountDetails.getRole())
.claim("authorities", accountDetails.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet()));
}
};
}
@Bean
OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource));
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}
I hope that the final token has the claims authorities and role, since I am using this method to authenticate the user right after being created, in later logins the refresh token is used or the authorization code by the BFF
Just add your customizer token to the jwt generator. Here is an example of how your token generator configuration would look:
@Bean
OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource));
jwtGenerator.setJwtCustomizer(tokenCustomizer());
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}
In the documentation, it teaches how to customize the opaque token. Unfortunately, they added only on the opaque. Link: Token customizer