Would CORS on OIDC authorization endpoint be a security risk?

Hello!

Context

I’m developing a number of microfrontend applications. Each application is lazily loaded into a larger SPA, and each application has it’s own stateless backend that it fetches data from, thus implementing Backend-For-Frontend pattern.

Since BFF is used, I intend on using confidential clients to access protected resources, including user information, only from backend.

However my frontend is still a statefull and with quite a complex state.

What I’m trying to do

To preserve frontend state I’d like to avoid redirects as much as possible.
My thinking was to go through standard authorization flow using XHR, so basically I’d like to do the following:

  1. Frontend requests a BFF /auth endpoint
  2. Backend redirects it to keycloak’s protocol/openid-connect/auth with prompt=none, response_type=code, redirect_uri and other parameters.
  3. Keycloak redirects back to BFF with either code or error parameters
  4. BFF uses code to get tokens and user credentials and securely store them in an encrypted cookie and returns 2xx; or 401 if error parameter is present.

I’m aware that authorization flow is redirect based and was intended for usage with Browser window, not XHR. But as I mentioned I’d like to limit browser redirects to preserve frontend state.

I’m also aware that this is usually done within the hidden iframe, but I’d like to limit number of iframes opened by microfrontend applications.

Where it fails

Since Keycloak runs on other domain than frontends, it requires CORS to follow redirect to BFF. Authorization endpoint does not include Access-Control-Allow-Origin header, and so redirects are ignored.

As far as I can tell no OAuth2 and OIDC providers support CORS for authorization endpoints, and this leads me to my question.

Question

Is absence of CORS on authorization endpoint is by design? What security risks would CORS pose?

I have several workarounds in mind, but I’m reluctant to implement them since no one seems to use described approach. Or maybe I’m missing something obvious?

Thanks for discussion!

How bad gyu is thinking about your idea:

Great idea. I just create hidden div/iframe which will try his IDP auth silently and obtained tokens will be forwarded and stored on my server. Then I can trick somehow muter/muter’s users to visit that page (probably some nice post about Oauth security will catch his interest) and I will have his/their token/indetities. If I’m lucky I will be able to get also admin permission to his Keycloak…

Generally OIDC protocol was designated by smart guys. Also Keycloak default settings are predefined by smart guys. All these people are trying to protect users (like me/you). You should to start siren :rotating_light: if you need to hack their ideas. That’s exactly what you are trying now.

Generally your frontend should manage own auth state (SPA = PKCE flow, token renewal with refresh tokens). API should return OK response or 401/403 response for unauthenticated (e.g. token expired)/forbidden (e.g. user is not authorized) requests. I don’t see a reason for auth redirects (it makes sense for web app, but not for SPA).

I would say token from frontend should be accepted by any your API. For me it looks like you are trying to solve problem, which you won’t have if you have better design.

1 Like

Can not agree more with what you said on starting siren. That’s the reason, I’m posting here before taking any action (and the reason I’m not sharing my workarounds).

My goal here is to understand my pitfalls and make sure no one will follow the erroneous path.

However, I think, I am missing something about your “bad guy” example. How does he redirects access tokens to malicious server? I’m not letting that tokens leave my backend unencrypted. Moreover, it seems to me, that if this type of attack was possible, whole authorization flow would be compromised.

Real danger may hide not within IFrame or redirects, but somewhere in XHR. I just can not understand where exactly.

Please, provide a little more detailed explanation for me to follow.

As for design you proposed, it’s possible, but It has some limitations.

  • Firstly, I am using different clients for different apps. That way I am able to manage resource access on per-app basis.
  • Secondly, I’d like to use confidential client, again to limit data access and token theft possibility. Btw it’s recommended to do so for BFF’s (OAuth 2.0 for Browser-Based Apps §6.2).

Thanks!

P.S. You got me. Good security discussion would be a perfect lure :slight_smile:

Idea: if you have SSO session, then I can reuse it and I can get your identity - snippet for security-admin-console (because it is a public client with well known redirect URL):

var keycloakRootUrl = "https://domain.com"
var realm = "master"
var clientId = "security-admin-console"
var code_verifier='JTnk2j_hg5rTHdZnxoyq2YewwRQpKO6gjx8oj2mezoM'
var code_challenge='h3COauLVb74biA4nJVfsPIukj9vbLpEwAZd40GrfCMc'

var authUrl = keycloakRootUrl + '/realms/' + realm + '/protocol/openid-connect/auth?client_id=' + clientId + '&code&scope=openid+email+profile&state=Lw%3D%3D&response_type=code&redirect_uri=' + keycloakRootUrl + '/admin/master/console/&code_challenge=' + code_challenge + '&code_challenge_method=S256'

fetch(authUrl).then(function (response) {
	console.log('success!', response);
}).catch(function (err) {
	console.warn('Something went wrong.', err);
});

Tested in the Firefox console. It can’t reach auth endpoint, because CORS issue - solved with “CORS Everywhere” plugin in a second. But I was wrong. I can reach auth endpoint, but I can’t reuse SSO session - it uses cookies with HttpOnly flag.
I guess you will have the same problem with your approach. Another protection in a place.