Customizing Tokens

Adding Roles to the Access Token

By default the access token has sub, aud, scope, iss, exp. Resource servers usually also want the user's roles. Add them with an OAuth2TokenCustomizer:

@Bean
OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer(CustomerRepository customers) {
    return context -> {
        if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
            Authentication principal = context.getPrincipal();
            Set<String> roles = principal.getAuthorities().stream()
                    .map(GrantedAuthority::getAuthority)
                    .filter(a -> a.startsWith("ROLE_"))
                    .map(a -> a.substring(5))
                    .collect(Collectors.toSet());
            context.getClaims().claim("roles", roles);
            context.getClaims().audience(List.of("shopease-api"));
        }
        if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
            customers.findByUsername(context.getPrincipal().getName()).ifPresent(c -> context.getClaims()
                    .claim("name", c.getFullName())
                    .claim("email", c.getEmail()));
        }
    };
}

The Result

Decoded token

Decode the payload part of the new access token:

echo "$ACCESS_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq

Example output

{
  "sub": "asha",
  "aud": ["shopease-api"],
  "nbf": 1772340000,
  "scope": ["openid", "orders.read"],
  "roles": ["CUSTOMER"],
  "iss": "http://localhost:9000",
  "exp": 1772340600,
  "iat": 1772340000,
  "jti": "b6a3e0d2-5f41-4c8e-9a7b-1d2e3f4a5b6c"
}

On the resource server, map this flat claim without custom code: JwtGrantedAuthoritiesConverter with setAuthoritiesClaimName("roles") and setAuthorityPrefix("ROLE_").

Note: Tokens are signed, not encrypted. Anyone holding a token can read its payload. Never put secrets, passwords, Aadhaar or card numbers in claims.

Practice

Exercise: Add a tier claim ("GOLD" / "SILVER") to access tokens only for the shopease-web client.

Show answer
@Bean
OAuth2TokenCustomizer<JwtEncodingContext> tierCustomizer(CustomerRepository customers) {
    return context -> {
        boolean accessToken = OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType());
        boolean webClient = "shopease-web".equals(context.getRegisteredClient().getClientId());
        if (accessToken && webClient) {
            customers.findByUsername(context.getPrincipal().getName())
                     .ifPresent(c -> context.getClaims().claim("tier", c.getTier().name()));
        }
    };
}