AuthorizationEndpoint does not support CORS

I’ve been banging my head against a problem we’ve been running into when keycloak and the quarkus server our under different subdomains leading to Cross-Domain-Request when the “Access Token Lifespan” is exceeded and the client application issues an XHR-Request to the REST-API.

I first suspected a problem in quarkus hence I filed a bug there https://github.com/quarkusio/quarkus/issues/10185 and created a small reproducer project https://github.com/tomsontom/keycloak-cors/ and after fixing a problem in my XHR-Request (it did not set the cookies) I still face CORS-Errors.

I cloned the Keycloak Code base and instrumed the code base a bit and so I see no code path the AuthorizationEndpoint could add CORS-Headers so - i hacked them into code base and now things work as expected. So the question is: Is it by design that AuthorzisationEndpoint (unlike eg TokenEndpoint) does not support CORS or is it a bug.

As I’m totally unfamiliar with the codebase I’m fairly certain that I did it in the completely wrong position (AuthorizationEndpoint#buildAuthorizationCodeAuthorizationResponse) but as I said it is working.

    private Response buildAuthorizationCodeAuthorizationResponse() {
    this.event.event(EventType.LOGIN);
    authenticationSession.setAuthNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
    
    Response r = handleBrowserAuthenticationRequest(authenticationSession, new OIDCLoginProtocol(session, realm, session.getContext().getUri(), headers, event), TokenUtil.hasPrompt(request.getPrompt(), OIDCLoginProtocol.PROMPT_VALUE_NONE), false);
    
    if( client != null ) {
    	String origin = headers.getRequestHeaders().getFirst(Cors.ORIGIN_HEADER);
    	if( origin != null ) {
    		Set<String> set = WebOriginsUtils.resolveValidWebOrigins(session, client);
    		if( set.contains(origin) ) {
    			r.getHeaders().add(Cors.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
                r.getHeaders().add(Cors.ACCESS_CONTROL_ALLOW_CREDENTIALS, true);
    		}
    	}
    }
    
	return r;
}

Looking at your application, it seems it is a public client making XHR requests to itself.

As such you should expect redirects from your application when the token expires, so that users are redirected to Keycloak to authenticate. Your frontend should expect 302 when that happens.

Considering your deployment, you should handle this behavior in your frontend so that when a redirect happens you automatically redirect the user to the login page to re-authenticate.

Note that the authorization endpoint is not supposed to support CORS because it is a redirect-based endpoint. You should not need CORS there.

no nothing is public at any point in time the flow is like this:

  • Request to localhost:8080/index.html
  • Quarks redirects to localhost:8081 to login
  • Sucessful login and keycloak redirects to localhost:8080
  • … time goes on (and token expires)
  • client sends XHR request to localhost:8080/hello endpoint
  • quarkus answers with 302 and location-header to localhost:8081/auth/…
  • browser calls above URL who answers with new cookies and redirect to localhost:8080/hello with a bunch of query-parameters

So I don’t see how I could ever step into the above series of calls nor I think that I as client need to know that the service is protected by keycloak this has to be transparent to me. What would you expect me to do on the client? I’d like to repeat - if keycloak and real application are access through the same domain (the default if i deploy that in our cluster in future) this is not a problem because the CORS error does not happen.

The question although is: Is it correct for quarkus to respond to my XHR-Call with 302 and the redirect or should that happen in quarkus?

By public, I mean the type of your client. No client credentials (https://github.com/tomsontom/keycloak-cors/blob/master/src/main/resources/application.properties#L5).

Ideally, you should have your frontend interacting using bearer tokens with your backend. I think that is the main design discussion here. Once you do that, your frontend should not get any 302 but 401 or 403 status code so that you can act accordingly.

Thinking more about the Quarkus side, we could potentially support a behavior where your application could decide whether or not to respond with a redirect. Something like the autodetect bearer token flag we have in our adapters.

By doing that, your application should not respond with a 302 when sending XHR requests, but 403 or 401. I see that as a potential improvement. So your frontend could act accordingly.

But yeah, from a Keycloak perspective, have CORS enabled to the authorization endpoint does not make sense. Mainly from a specification perspective.

Or maybe you can try to implement a filter in your application that could change the response so that you set 401 or 403 depending on whether or not the request is XHR.

On public: Yes you are right but the same problem is when i use “confidential” and the secret - I thought with public you meant REST-Endpoint/Webpage is not protected by Keycloak.

On 401/403: The current behavior of 302 is perfectly fine if keycloak and application run on the same domain and in fact from a developer point of view it is completely transparent, I don’t understand why i should take any action as a developer to mitigate the reason that my application is protected through an authententification service (be it keycloak or something else)

So lets’s assume I manage to change the response from 302 to 401/403 (if not mistaken it is impossible to handle a 302-redirect in client-side JavaScript) I’m uncertain what should follow afterwards. The auth-session is still valid so I certainly don’t want to show a login dialog. What endpoint would you suggest me to call upon that so I then can once more call my REST endpoint?

I hope I’m not asking very silly questions.

My use case is as follows.

I’m using a OAuth2-Proxy (acting as a confidential client) as a proxy to upstream static image resources (map tiles to be more specific).

The setup is as follows,
Keycloak domain: https://auth.local
SPA domain: webclient.local
Protected Static Server domain: map.local (or it could be webclient.local as well)

After logging in to webclient.local via Auth Code Flow, embedding the image using <img src="http://map.local/...> tag works as it is not a CORS requests (Keycloak cookies gets sent through the redirect, resulting in seamless redirect to image resource).

Using fetch api will result in 302 to auth endpoint which fails.

Yet to find a solution.