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:
-
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
-
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
-
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