How to add more data in access_token JWT

Eniss picture Eniss · Mar 22, 2018 · Viewed 8k times · Source

I am trying to add new fields in JWT token which is actually access_token which is generated with grant_type=password. I want to add more fields if grant type is only password.

If I implement a custom token enhancer, it adds new fields in the response body of oauth login api. But I only need those new fields inside access_token JWT.

e.g.:

when decoding access_token, Object should be from

{
  "user_name": "uuid",
  "scope": [
    "trust"
  ],
  "exp": 1522008499,
  "authorities": [
    "USER"
  ],
  "jti": "9d827f63-99ba-4fc1-a838-bc74331cf660",
  "client_id": "myClient"
}

to

{
  "user_name": "uuid",
  "newField": [
    {
      "newFieldChild": "1",
    },
    {
      "newFieldChild": "2",
    }
  ],
  "scope": [
    "trust"
  ],
  "exp": 1522008499,
  "authorities": [
    "USER"
  ],
  "jti": "9d827f63-99ba-4fc1-a838-bc74331cf660",
  "client_id": "myClient"
}

Implementing CustomTokenEnhancer adds newField list in the response body of login:

{
    "access_token": "jwt-access_token",
    "token_type": "bearer",
    "refresh_token": "jwt-refresh_token",
    "expires_in": 299999,
    "scope": "trust",
    "jti": "b23affb3-39d3-408a-bedb-132g6de15d7",
    "newField": [
      {
        "newFieldChild": "1",
      },
      {
        "newFieldChild": "2",
      }
    ]
}

CustomTokenEnhancer:

public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(
            OAuth2AccessToken accessToken,
            OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
        Map<String, String> newFields = ....;
        additionalInfo.put("newField", newFields);
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    }
}

is it possible to modify access_token JWT if grant_type is password?

Answer

Tarun Lalwani picture Tarun Lalwani · Apr 19, 2018

Your question is quite similar/same to below SO thread

Spring OAuth 2 + JWT Inlcuding additional info JUST in access token

I will just make it a bit easier to understand. There are two things

  • A access token enhancer which enhances your token to include more information
  • A token converter which converts the token to output you see in API

So what you want it is below

  • Access token enhancer should see the additional properties
  • Access token converter should not see the additional properties

Below is the class that I actually used

package org.baeldung.config;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfigJwt extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("sampleClientId")
            .authorizedGrantTypes("implicit")
            .scopes("read", "write", "foo", "bar")
            .autoApprove(false)
            .accessTokenValiditySeconds(3600)

            .and()
            .withClient("fooClientIdPassword")
            .secret("secret")
            .authorizedGrantTypes("password", "authorization_code", "refresh_token")
            .scopes("foo", "read", "write")
            .accessTokenValiditySeconds(3600)
            // 1 hour
            .refreshTokenValiditySeconds(2592000)
            // 30 days

            .and()
            .withClient("barClientIdPassword")
            .secret("secret")
            .authorizedGrantTypes("password", "authorization_code", "refresh_token")
            .scopes("bar", "read", "write")
            .accessTokenValiditySeconds(3600)
            // 1 hour
            .refreshTokenValiditySeconds(2592000) // 30 days
        ;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter()));
        endpoints.tokenStore(tokenStore())
            .tokenEnhancer(tokenEnhancerChain)
            .authenticationManager(authenticationManager);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        final JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                if(authentication.getOAuth2Request().getGrantType().equalsIgnoreCase("password")) {
                    final Map<String, Object> additionalInfo = new HashMap<String, Object>();
                    additionalInfo.put("organization", authentication.getName() + randomAlphabetic(4));
                    ((DefaultOAuth2AccessToken) accessToken)
                            .setAdditionalInformation(additionalInfo);
                }
                accessToken = super.enhance(accessToken, authentication);
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(new HashMap<>());
                return accessToken;
            }
        };
        // converter.setSigningKey("123");
        final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
        return converter;
    }

//    @Bean
//    public TokenEnhancer tokenEnhancer() {
//        return new CustomTokenEnhancer();
//    }

}

The original code is on below

https://github.com/Baeldung/spring-security-oauth

Doesn't include my changes though, but above code is enough

Testing

Body

As you can see body doesn't contain the additional properties

Access token

As you can see the access token has the additional properties. Also your requirement for only grant_type as password is met through

if(authentication.getOAuth2Request().getGrantType().equalsIgnoreCase("password")) {