spring-security

Sessionless form login for Spring OAuth2 authorization server?


I would like to avoid leaving a login session open after an OAuth2 authorization code (spring-authorization-server) has been issued. Currently the flow looks like this:

  1. The user requests /oauth2/authorize with a redirect_uri.
  2. If there isn't an authenticated session, the response is a 302 to /login. The original request URL is saved in a session.
  3. The user submits the login form and, if everything checks out, Spring will issue a 302 redirect to the previously saved URL.
  4. This time, /oauth2/authorize will see the authenticated session and redirect the user back to the redirect_uri with a code.

The session then remains, allowing the user to keep fetching new access tokens without re-authenticating.

Using stateless session management, I’ve toyed with CookieRequestCache to keep track of the original request URL in step 2. This works, but I would need some sort of internal redirect between step 3 and 4 or else the authentication state get lost. The browser would then be redirected from /login straight to redirect_uri. Is there a way to do this?

And if all else fails, is there way to write a hook that executes just prior to the redirect to redirect_uri, deleting the session?


Solution

  • You can customize the response handling of /oauth2/authorize in Spring Authorization Server through its OAuth2AuthorizationEndpointConfigurer DSL.

    You can take a look at authorizationResponseHandler. A custom version could invalidate the session.

    Caveat

    That said, requesting to immediately log out the user is a bit uncommon. Indeed, a common use case for an authorization server is to provide single-sign-on across multiple applications. If you choose to invalidate the session, then when the user visits a second application (perhaps introduced down the road), then they will need to log in again for each application that they visit.

    With that caveat in mind, here is an example of what a custom AuthenticationSuccessHandler could look like:

    public class MySessionInvalidatingAuthorizationResponseHandler
        implements AuthenticationSuccessHandler {
    
        private final var redirectStrategy = new DefaultRedirectStrategy();
    
        @Override
        public void onAuthenticationSuccess(
            HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) {
            
            var result = (OAuth2AuthorizationCodeRequestAuthenticationToken)
                authentication;
            var code = result.getAuthorizationCode().getTokenValue();
            var builder = UriComponentsBuilder
                .fromUriString(result.getRedirectUri())
                .queryParam(OAuth2ParameterNames.CODE, code);
            if (StringUtils.hasText(result.getState())) {
                builder.queryParam(OAuth2ParameterNames.STATE, result.getState());
            }
    
            // invalidate the session ...
    
            this.redirectStrategy.sendRedirect(
                request, response, builder.toUriString());
        }
    }
    

    Alternatively, you could implement SecurityContextRepository to store the SecurityContext in a cookie; however, this comes with a still stronger caveat that storing the security context as a cookie is much harder to secure. In this setup, /oauth2/authorize would work by default since it would have a materialized SecurityContext available. This may be more of the same, though, since if your primary concern is immediately logging the individual out, you would still need to delete the cookie.

    UPDATE: One additional thought is that maybe you are actually not wanting to log in the user at all. In that case, the client_credentials flow may be more appropriate. It is a back-end grant flow where the client's id and password are used to get an access token.