spring-securityspring-oauth2spring-authorization-server

Additional verification logic when refreshing an access token


I am working on implementing access and refresh tokens with OAuth 2.0. I have implemented a custom grant type, and I can renew the access token using a refresh token with already existing OAuth2RefreshTokenAuthenticationConverter and OAuth2RefreshTokenAuthenticationProvider. The problem is that, before granting a new access token, I need to call the database to verify that the user is still active and perform additional checks (e.g., to determine if they have lost their login eligibility in the meantime). Ideally, I need a call to authenticationManager.authenticate before granting a new access token.

I've tried to implement a OncePerRequest filter for additional validation, but inside it I have no access to the user's username, only to the refresh token, grant type, and scope.

Is there a filter which accepts OAuth2AuthenticationToken and runs just before OAuth2RefreshTokenAuthenticationProvider does?

I also considered copying the entire OAuth2RefreshTokenAuthenticationConverter and adjusting it accordingly. However, I would like to avoid creating a copy of the converter due to maintainability concerns.

Thanks in advance!


Solution

  • Write a custom AuthenticationProvider to perform additional checks. It will run before OAuth2RefreshTokenAuthenticationProvider runs. For example:

    /**
     * If the user account is locked, then throws an exception, which stops subsequent
     * {@link AuthenticationProvider#authenticate AuthenticationProvider}s being tried.
     */
    @RequiredArgsConstructor
    public class UserDetailsRefreshTokenAuthenticationProvider implements AuthenticationProvider {
    
      private final CustomUserDetailsService userDetailsService;
    
      @Override
      public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        var authenticationToken = (OAuth2RefreshTokenAuthenticationToken) authentication;
        String refreshToken = authenticationToken.getRefreshToken();
        if (userDetailsService.isAccountLocked(refreshToken)) {
          throw new LockedException("User account is locked");
        }
    
        return null;
      }
    
      @Override
      public boolean supports(Class<?> authentication) {
        return OAuth2RefreshTokenAuthenticationToken.class.isAssignableFrom(authentication);
      }
    }
    

    Add this custom AuthenticationProvider into the token endpoint:

    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(
        HttpSecurity http,
        UserDetailsRefreshTokenAuthenticationProvider userDetailsRefreshTokenAuthenticationProvider
    ) throws Exception {
      OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    
      http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
          .tokenEndpoint(endpoint -> endpoint              
              .authenticationProvider(userDetailsRefreshTokenAuthenticationProvider)
    
      // ...