I cannot get new access token and refresh token using Spring boot and cookies for tokens

I’m developing a SPA with Nuxt.js for the frontend and Spring boot for the backend.

Due to project constrains I had to make my Spring boot backend work both for 1) logging in users and 2) dealing with resource authorization

That means that the flow is like follows:

  1. The user clicks on “login” button and opens http://localhost:8080/oauth2/authorization/okta?redirect_uri=http://localhost:3000/user/redirect, then is redirected to Auth0 to log in using one of the available providers

  2. After a successfull login, he is sent back to the backend that registers the user in the DB, creates 2 cookies using the access and the refresh tokens, and send the user back to the frontend at the URL: http://localhost:3000/user/redirect

  3. The user tries to access to a protected resource, the backend checks the access token and if the user has the permission, answers providing the requested data. If the user doesn’t have the permission, answers with a 401.

Now everything described above works with no issues and I was also able to play with Auth0 permissions correctly.

I’ve set up refresh tokens with rotation in Auth0 control panel.

Now, what is the issue?

The issue is that I don’t know how can I use the refresh token provided in cookies in order to get new access and refresh tokens.

Here is some more context about my backend Spring boot app.

This is my security config:

package com.gootrix.security.config;

import com.gootrix.security.oauth2.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Autowired
  private CustomOidcUserService customOidcUserService;

  @Autowired
  private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;

  @Autowired
  private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;

  @Autowired
  private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;

  @Autowired
  CustomAuthorizationRequestResolver customAuthorizationRequestResolver;

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .cors()
              .and()
            .csrf()
              .disable()
            .sessionManagement()
              .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
              .and()
            .authorizeHttpRequests()
              .requestMatchers("/",
                    "/robots.txt",
                    "/error",
                    "/favicon.ico",
                    "/*/*.png",
                    "/*/*.gif",
                    "/*/*.svg",
                    "/*/*.jpg",
                    "/*/*.html",
                    "/*/*.css",
                    "/*/*.js")
                    .permitAll()
              .requestMatchers("/api/auth/*").permitAll()
              .requestMatchers("/api/test/all").permitAll()
              .requestMatchers("/api/test/user").authenticated()
              .requestMatchers("/api/test/admin").hasAuthority("read:admin")
              .requestMatchers("/api/payment/*")
                .permitAll()
              .requestMatchers("/oauth2/*")
                .permitAll()
              .requestMatchers("/login*")
                .permitAll()
              .anyRequest()
                .authenticated()
              .and()
            .oauth2Login()
              .authorizationEndpoint()
                .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)
                .authorizationRequestResolver(customAuthorizationRequestResolver)
                .and()
              .userInfoEndpoint()
                .oidcUserService(customOidcUserService)
                .and()
              .successHandler(oAuth2AuthenticationSuccessHandler)
              .failureHandler(oAuth2AuthenticationFailureHandler);

    http.oauth2ResourceServer((oauth2ResourceServer) ->
            oauth2ResourceServer
                    .jwt()
    );

    return http.build();
  }
}

This is the authentication success handler that writes the cookies:

package com.gootrix.security.oauth2;

import com.gootrix.config.AuthProps;
import com.gootrix.config.Oauth2Props;
import com.gootrix.security.principal.UserPrincipal;
import com.gootrix.security.tokens.csrf.CsrfTokenService;
import com.gootrix.util.CookieUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;

import static com.gootrix.security.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME;

@Component
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Autowired
    private AuthProps authProps;

    @Autowired
    private Oauth2Props oauth2Props;

    @Autowired
    private HttpCookieOAuth2AuthorizationRequestRepository cookieRepository;

    @Autowired
    private CsrfTokenService csrfTokenService;

    @Autowired
    private OAuth2AuthorizedClientService clientService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

        String targetUrl = determineTargetUrl(request, response, authentication);

        if (response.isCommitted()) {
            logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
            return;
        }

        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
        OAuth2AuthorizedClient authorizedClient = clientService.loadAuthorizedClient("okta", principal.getEmail());

        String userCookieValue = "{\"firstName\":\""+principal.getFirstName()+"\"," +
                "\"lastName\":\""+principal.getLastName()+"\"," +
                "\"email\":\""+principal.getEmail()+"\"," +
                "\"img\":\""+principal.getImg()+"\"," +
                "\"csrfToken\":\""+csrfTokenService.generateCsrfToken(principal.getUsername())+"\"}";
        CookieUtils.addCookieSameSite(response, authProps.getUserCookieName(),
                userCookieValue, authProps.getCsrfCookieExpirationS(), false);

        CookieUtils.addCookieSameSite(response, authProps.getAccessCookieName(),
                authorizedClient.getAccessToken().getTokenValue(), authProps.getAccessCookieExpirationS(), true);
        CookieUtils.addCookieSameSite(response, authProps.getRefreshCookieName(),
                authorizedClient.getRefreshToken().getTokenValue(), authProps.getRefreshCookieExpirationS(), true);

        clearAuthenticationAttributes(request, response);
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }

    protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) {
        String redirectUri = CookieUtils.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME);
        if(redirectUri != null && !isAuthorizedRedirectUri(redirectUri)) {
            throw new BadRequestException("Sorry! We've got an Unauthorized Redirect URI and " +
                    "can't proceed with the authentication");
        }

        String targetUrl = redirectUri == null ? getDefaultTargetUrl() : redirectUri;
        return UriComponentsBuilder.fromUriString(targetUrl).build().toUriString();
    }

    protected void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) {
        super.clearAuthenticationAttributes(request);
        cookieRepository.removeAuthorizationRequestCookies(request, response);
    }

    private boolean isAuthorizedRedirectUri(String uri) {
        URI clientRedirectUri = URI.create(uri);
        URI authorizedURI = URI.create(oauth2Props.getAuthorizedRedirectUri());
        if(authorizedURI.getHost().equalsIgnoreCase(clientRedirectUri.getHost())
                && authorizedURI.getPort() == clientRedirectUri.getPort()) {
            return true;
        }
        else return false;
    }
}

And this is the code I use to read from the access token cookie the bearer JWT token


package com.gootrix.security.oauth2;

import com.gootrix.config.AuthProps;
import com.gootrix.util.CookieUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.stereotype.Component;

@Component
public class CookieTokenResolver implements BearerTokenResolver {

    @Autowired
    AuthProps authProps;

    @Override
    public String resolve(final HttpServletRequest request) {
        return CookieUtils.getCookie(request, authProps.getAccessCookieName());
    }
}

Everything works fine but I can’t understand how can I refresh the access and refresh tokens. I wasn’t able to find any documentation online.

Could you please help me?

Thanks