Search code examples
springoauth-2.0jwt

How to get the refresh token in a spring OAuth2 client


I'm developing a Spring application which acts as an OAuth2 client and Spotify is the resource server. This is my configuration:

spring:
  security:
    oauth2:
      client:
        registration:
          spotify:
            client-id: ...
            client-secret: ...
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
            scope: user-read-private, user-read-email
            client-name: Spotify
            client-alias: spotify
        provider:
          spotify:
            authorization-uri: https://accounts.spotify.com/authorize
            token-uri: https://accounts.spotify.com/api/token
            user-info-uri: https://api.spotify.com/v1/me
            user-name-attribute: display_name

My problem is that I just can't find how to get the refresh token that is sent by Spotify in the response of /api/token

This is how the Spotify response looks like: (Source: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow)

spotify response

I tried to implement my own CustomUserService like this:

.and()
  .userInfoEndpoint()
  .userService(customUserService)

inside my CustomUserService I tried to overload the following method: public OAuth2User loadUser(OAuth2UserRequest userRequest)

In this OAuth2UserRequest object I can find the access token but there is absolutely no information about the refresh token:

Access Token

I'm thinking about I need some additional config to put the refresh_token in the additionalParameters object but I can't find anything like this.

Is there any way I can get the refresh token in my code and do stuff with that?


Solution

  • So I figured out a way to overcome this. The first thing needed is to include the accessTokenResponseClient in the security config with a custom implementation.

    Security Config:

    ...
    .and()
      .tokenEndpoint()
      .accessTokenResponseClient(accessTokenResponseClient())
    ...
    

    And the key part here is to set our CustomTokenResponseConverter:

    @Bean
      public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
            new DefaultAuthorizationCodeTokenResponseClient();
    
        OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter =
            new OAuth2AccessTokenResponseHttpMessageConverter();
        tokenResponseHttpMessageConverter.setTokenResponseConverter(new CustomTokenResponseConverter());
        RestTemplate restTemplate = new RestTemplate(Arrays.asList(
            new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
    
        accessTokenResponseClient.setRestOperations(restTemplate);
        return accessTokenResponseClient;
      }
    

    In this converter it is possible to access the refresh token and for example put it in the additionalParameters map that is mentioned in the question:

    public class CustomTokenResponseConverter implements
        Converter<Map<String, String>, OAuth2AccessTokenResponse> {
    
      @Override
      public OAuth2AccessTokenResponse convert(Map<String, String> tokenResponseParameters) {
        String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);
        String refreshToken = tokenResponseParameters.get(OAuth2ParameterNames.REFRESH_TOKEN);
        long expiresIn = Long.parseLong(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN));
    
        Set<String> scopes = Collections.emptySet();
        if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
          String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE);
          scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, " "))
              .collect(Collectors.toSet());
        }
    
        Map<String, Object> additionalParameters = new HashMap<>();
        additionalParameters.put(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken);
    
        return OAuth2AccessTokenResponse.withToken(accessToken)
            .tokenType(OAuth2AccessToken.TokenType.BEARER)
            .expiresIn(expiresIn)
            .scopes(scopes)
            .refreshToken(refreshToken)
            .additionalParameters(Collections.unmodifiableMap(additionalParameters))
            .build();
      }
    }
    

    This way it can be accessed among the additionalParameters in the custom user service the following way:

    String refreshToken = (String) userRequest.getAdditionalParameters().get(OAuth2ParameterNames.REFRESH_TOKEN);