5

How does my Spring Boot client application access a refresh token supplied by e.g. Google in Spring Security 5?

Pretty simple question. The remote authorization server (e.g. Google) sends a refresh token, and I want to use it. What's the best way to persist and retrieve it in Spring Security 5?

It seems this answer, this question, and this exernal link describe an approach no longer compatible since Oauth2 became a first-class citizen in Spring Security 5.

Context:

Refresh tokens allow a client application to continue to access resources after a user's session has expired. Per Google's docs, refresh tokens should be persistent:

The application should store the refresh token for future use and use the access token to access a Google API.

Spring security makes the access token widely available in the form of an OAuth2AuthenticationToken, but the refresh token is not included there.

The refresh token is also not available in the OidcUserService (or a class that overrides it), since public OidcUser loadUser(OidcUserRequest userRequest) does not have access to the refresh token. This is a bummer, since it would be nice to override OidcUserService with a custom class that creates/retrieves a user from their OIDC user details and saves their associated refresh token at the same time.

The OAuth2LoginAuthenticationFilter saves the refresh token in the ClientRegistrationRepository:

OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
    authenticationResult.getClientRegistration(),
    oauth2Authentication.getName(),
    authenticationResult.getAccessToken(),
    authenticationResult.getRefreshToken());

this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);

The default implemetation keeps the token in transient memory, which is not suitable for distributed applications or persisting across restarts.

It seems there is a JdbcOauth2AuthorizedClientService, with docs recently added, and a schema that suggests that it could be useful, but no example was provided either of configuring it or of using it to retrieve a refresh token.

So how can a client application persist and access refresh token in Spring Security 5?

Nate Vaughan
  • 3,471
  • 4
  • 29
  • 47

1 Answers1

9

JdbcOauth2AuthorizedClientService would indeed be appropriate for your use case. It is very simple to configure. First, you need to add this table to your database:

CREATE TABLE oauth2_authorized_client (
  client_registration_id varchar(100) NOT NULL,
  principal_name varchar(200) NOT NULL,
  access_token_type varchar(100) NOT NULL,
  access_token_value blob NOT NULL,
  access_token_issued_at timestamp NOT NULL,
  access_token_expires_at timestamp NOT NULL,
  access_token_scopes varchar(1000) DEFAULT NULL,
  refresh_token_value blob DEFAULT NULL,
  refresh_token_issued_at timestamp DEFAULT NULL,
  created_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
  PRIMARY KEY (client_registration_id, principal_name)
);

Then, configure the JdbcOauth2AuthorizedClientService bean:

@Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService
        (JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository) {
    return new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository);
}

Please note that the current implementation has a bug which will be solved with spring-security version 5.3.2 that is due in a few days from now.

Stav Shamir
  • 888
  • 7
  • 20
  • 1
    Do you know if this is only supported for certain databases? I'm attempting to use this with Postgres 12 and am getting exceptions coercing the token values (byte[]) into Types.BLOB. PgPreparedStatement is expecting the datatype to be either an instanceof Blob or InputSteam when Types.BLOB is used. As the 'blob' datatype was not available in Postgres, I've tried with both OID, BYTEA, and TEXT, but the issue seems to be the clashing of using java.sql.Types.BLOB with a byte[] in Postgres (driver 42.2.12) – Josh Collins May 15 '20 at 01:56
  • @JoshCollins It is supposed to support all standard features of SQL DBs. You should open an issue at https://github.com/spring-projects/spring-security/issues – Stav Shamir May 16 '20 at 10:53
  • @StavShamir If I'm currently only using Spring JPA in my project, should I still use JdbcOauth2AuthorizedClientService or write my own service using JPA? – Andreas Aug 16 '20 at 17:50
  • 2
    @Andreas you can use `JdbcOauth2AuthorizedClientService` if you, but you can also write your own with JPA. If you don't need a custom behavior, I think it is best to use the provided service. – Stav Shamir Aug 17 '20 at 09:06
  • 2
    As of Spring Security 5.4.1 (the latest as of October 2020), `JdbcOauth2AuthorizedClientService` doesn't work with Postgres (unless you do additional configuration). I'm trying to get that fixed, see https://github.com/spring-projects/spring-security/pull/9070 – candrews Oct 16 '20 at 18:06