Expected Behavior
Would great if with authorizationFailureHandler it would be possible to provide a custom error response to the client.
Current Behavior
Currently DefaultOAuth2AuthorizedClientManager supports setting failure handler but there two problems with it.
1. If I call setAuthorizationFailureHandler the authorized client will be not removed from authorized client repository. Which is not great as I would like to keep this behavior. So I would need to copy over this from Spring code.
2. It's not possible to send a custom error response in authorizationFailureHandler because on next line(https://github.com/spring-projects/spring-security/blob/main/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java#L181) the error is still rethrown and the original redirection behavior is kept. So in the end I would get a "Cannot create a session after the response has been committed" exception if I would try to send custom response in authorizationFailureHandler.
Context
I have a REST base microservice in Kubernetes cluster consuming OAuth2 provider with authorization code grant flow using Spring OAuth2 client. I would like to respond with 401 and provide a login url as part of Spring HATEOAS in case of oauth2 client authorization error to the SPA.
The only workaround I am aware of is
1. manually remove the authorized client from authorized client repository
2. throw a custom error in authorizationFailureHandler
3. add a custom Filter before OAuth2AuthorizationRequestRedirectFilter which would catch the custom error and do the custom error response for SPA instead of redirection as we are in scope of REST API.
Comment From: sjohnr
Hi @mucsi96!
See this comment on a related issue. It seems that a similar approach would work here. Assuming you have a custom endpoint, for example GET /authorize, you can add an exception handler for the OAuth2AuthorizationException. For example:
@RestControllerAdvice
public class MyControllerAdvice {
private static final Set<String> UNAUTHORIZED_ERRORS = new HashSet<>(Arrays.asList(
"invalid_scope",
"..."
));
@ExceptionHandler(OAuth2AuthorizationException.class)
public ResponseEntity<OAuth2Error> handleError(OAuth2AuthorizationException ex) {
String errorCode = ex.getError().getErrorCode();
if (UNAUTHORIZED_ERRORS.contains(errorCode)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getError());
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getError());
}
}
I used a similar approach recently in this branch of Spring Authorization Server, where I respond to an XHR request with a 401 on error.
While I would agree that customizing the error handling to produce a custom response in the OAuth2AuthorizedClientManager may not be feasible, it does seem possible in the wider context of a Spring application without requiring a custom filter. I believe the OAuth2AuthorizedClientManager is really designed as a convenient bridge between the framework and application-specific behavior to actually enable these kinds of use cases without requiring customization/configuration.
Does this workaround help generate any ideas for an approach for you?
Comment From: mucsi96
Hi @sjohnr! Thx a lot for very helpful suggestion. It resolved my issue. My solition:
@RestControllerAdvice
public class AppControllerAdvice {
@ExceptionHandler(ClientAuthorizationRequiredException.class)
public ResponseEntity<RepresentationModel> handleError(ClientAuthorizationRequiredException ex) {
String oauth2LoginUrl = ServletUriComponentsBuilder.fromCurrentServletMapping().path(
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" + ex.getClientRegistrationId()
).build().toString();
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(RepresentationModel
.of(null)
.add(Link.of(oauth2LoginUrl).withRel("oauth2Login")));
}
}
Comment From: mucsi96
I guess for oauth logic error handling I should use http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);