johnyka
johnyka

Reputation: 409

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?

Upvotes: 5

Views: 2280

Answers (1)

johnyka
johnyka

Reputation: 409

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);

Upvotes: 2

Related Questions