Pass Http Request to OAuth2AuthorizationRequestResolver#authorizationRequestCustomizer
Expected Behavior
It would be nice if we could use ServerWebExchange when customizing the Authorization Request using DefaultServerOAuth2AuthorizationRequestResolver#authorizationRequestCustomizer
At the moment, we only get the builder instance.
Current Behavior
Context
I would like to add additional query params to skip or enable login screen on Keycloak using kc_idp_hint query param, depending on where the user is coming from.
@ZIRAKrezovic, can you please share more about your use case? What request material are you looking to use, what have you tried so far, and are there any workarounds you're aware of?
Hello @sjohnr. I have the following use-case
- Keycloak installed as a Broker to Microsoft, but also supporting internal users that do not have a SSO account
- Spring Security configured to use Keycloak
To eliminate a step for Microsoft SSO users, we pass "kc_idp_hint" to keycloak's own OAuth2 Authorization endpoint to skip the Keycloak login form and redirect to Microsoft SSO
But we'd like to have a workaround to show the login form - to stop the "kc_idp_hint" from being added to the call to OAuth2 Authorization Request in certain cases.
So the use case is as follows - if the user is not logged in and they access the application with special query parameter "show-login-page=true" the kc_idp_hint query parameter is not sent to Keycloak Authorization Endpoint.
Since I cannot access query parameters in DefaultServerOAuth2AuthorizationRequestResolver#authorizationRequestCustomizer I am left with the following
- Define a
ServerAuthenticationEntryPointto pass the "kc_idp_hint" query param to spring security "/oauth2/authorization/{registrationId}" if the source request contains query parameter "show-login-page=true" - Reparse the redirect URL constructed by
DefaultServerOAuth2AuthorizationRequestResolverin aServerRedirectStrategyused byOAuth2AuthorizationRequestRedirectWebFilterand add the kc_idp_hint to the target URL if the query param was present in the request handled byOAuth2AuthorizationRequestRedirectWebFilter
Relevant code used
return http .exceptionHandling(exceptionHandlingSpec ->
exceptionHandlingSpec.authenticationEntryPoint(authenticationEntryPoint()))
.oauth2Login(loginSpec ->
loginSpec.authorizationRedirectStrategy(authorizationRedirectStrategy())
Where the authenticationEntryPoint() and authorizationRedirectStrategy() are two implementations I mentioned earlier.
This is quite a lot to override and also provides additional overhead when re-parsing the authorization redirect URL and modifying it.
Thank you for sharing your use case in more detail, @ZIRAKrezovic. That is helpful.
So first off, I would mention that if you require involving the authentication entry point, I don't think you avoid providing one, since it seems you are trying to receive a request to some other endpoint in the application with the show-login-page parameter.
I don't think that makes sense in all cases, because it may mean you are exposing a parameter on an endpoint that parameter is not meant for. For example, a web application with /resource1?show-login-page=true would need to redirect to /oauth2/authorization/keycloak?show-login-page=true using the authentication entry point. This seems unusual, and a better solution would be browse directly to the redirection endpoint in the frontend. For example, with a link like <a href="/oauth2/authorization/keycloak?show-login-page=true">Log In with Keycloak</a>. Alternatively, you could also expose your own login endpoint, like /login?show-login-page=true, and redirect to /oauth2/authorization/keycloak?... from there. This might make more sense. Anyway, that's just advice that might help you.
Regarding this issue, we will focus on the redirection endpoint (/oauth2/authorization/{registrationId}). I usually recommend delegation for cases like this. For example,
class KeycloakIdpHintServerOAuth2AuthorizationRequestResolver implements ServerOAuth2AuthorizationRequestResolver {
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
public KeycloakIdpHintServerOAuth2AuthorizationRequestResolver(ReactiveClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
}
@Override
public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange) {
var delegate = new DefaultServerOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
var showLoginForm = exchange.getRequest().getQueryParams().getFirst("show-login-form");
if (!Objects.equals(showLoginForm, "true")) {
delegate.setAuthorizationRequestCustomizer((builder) ->
// TODO: Provide kc_idp_hint
builder.additionalParameters(Map.of("kc_idp_hint", "...")));
}
return delegate.resolve(exchange);
}
@Override
public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange, String clientRegistrationId) {
throw new UnsupportedOperationException(); // The overloaded variant is not called by the filter
}
}
Using delegation gets you access to the ServerWebExchange so you can negotiate an optional parameter on the endpoint and translate it into customizer logic. So this is probably the nicest workaround I can suggest for this case. Please try this workaround and let me know if it suffices for your case.
Based on your feedback, we can decide whether we need to expose the ServerWebExchange in the builder. I think it would only be for convenience to make this a simpler customization. Again, I don't consider customizing the authentication entry point part of the consideration here, because this enhancement would not remove the need for one. Let me know if you see this differently.
Hi @sjohnr. Thanks for the feedback.
I am aware that the usage of random query param on all protected resources may be unnecessary and could pose an issue, but it is just to make (at most 2) users happy who use this functionality. Since there's a functionality that restores the caller URL after OIDC Redirect, I simply use it to strip the "show-login-page" query param, before saving it to request cache and forwarding the user to default entrypoint.
The main functionality, as you stated, is handled in Redirect Filter and Request Resolver, and it always requires that /oauth2/authorization/keycloak is called with query parameter kc_idp_hint - no matter how - directly by user or indirectly by hacking redirect logic in exception handler.
So let me try to rephrase this request: Would you consider a "pass these request parameters to OAuth2 authentication endpoint" feature instead of exposing ServerWebExchange in Customizer
Example: As I said above, I call /oauth2/authorization/keycloak?kc_idp_hint=microsoft and would need a way to tell Request Resolver "If you received a "kc_idp_hint" parameter, pass it on to authorization request" ... It would make the implementation have a simple list of allowed query parameters to pass from the filter handling the redirect to the OAUth2 authorization endpoint.
Keycloak offers that exact feature for brokering, to allow passing query params received to broker auth endpoint to the brokered auth endpoint - but only those you specify that can be passed through.
Hi @ZIRAKrezovic,
So let me try to rephrase this request: Would you consider a "pass these request parameters to OAuth2 authentication endpoint" feature instead of exposing ServerWebExchange in Customizer
Are you asking about a "feature" of an authentication entry point that passes these parameters in the redirect to the endpoint?
Hi @sjohnr - yes, in short
The feature would allow any parameters passed to /oauth2/authorization/{registrationId} to be passed through to OAuth2 Authorization Endpoint when allowed.
@ZIRAKrezovic, I may have misunderstood you so I don't think we're on the same page here. I would like to go back and just focus on the existing customization capabilities of ServerOAuth2AuthorizationRequestResolver and the authorizationRequestCustomizer and how to improve them.
First, please let me know whether you can achieve your desired use case with the suggested code snippet above (or similar). We're trying to figure out whether it's possible to achieve your use case without resorting to things like customizing the RedirectStrategy and re-parsing the redirect uri. I believe the code sample I provided will move you forward and we can determine further improvements from there. Please let me know if it does not move you forward and provide details.
Hi @sjohnr - the example would work as you've provided. Still a bit more code than I'd like to write, but it's not too bad.