Multi-Tenancy - realm resolution based on username (email address)

TL;DR

We’re trying to create a SaaS with multi-tenancy, and would like to extract the tenant information (and hence the Keycloak realm to use) based on the username the user is trying to login as.

Is that possible? How?

Details

Determining the realm during login

Deciding which tenant a login belongs to can be done by either putting the tenant information in the application hostname, e.g. https://customer1.oursaas.com or by looking at the username entered by the user, e.g. fred.flintstone@customer1.com.

And we’d like to extract the tenant/realm customer1.com from the username entered. But how do we get around the chicken-and-egg situation, where we need to include the realm in the Keycloak URL but the realm is only known after the user has entered his username on the Keycloak server?

The Multi Tenancy documentation has a code example that shows code selecting a tenant based on path and realm variables. First of all I think path and realm are undefined in the example (tracked separately in KEYCLOAK-15065). But more importantly, the only input to:

public KeycloakDeployment resolve(OIDCHttpFacade.Request request)

is the HTTP request, so it doesn’t reveal how it is possible to extract the tenant/realm from the username. My guess is also that this method is called before the username has been entered by the user.

Multiple Keycloak Instances are Required

Further, Multi Tenancy says:

Multi Tenancy, in our context, means that a single target application (WAR) can be secured with multiple Keycloak realms. The realms can be located on the same Keycloak instance or on different instances.

And because of limitations on the number of realms per Keycloak instance we should be prepared to have multiple Keycloak instances since we will have many tenants and hence many realms.

So when the user hits the login link, we need to know both the Keycloak server and realm immediately in order to create the Keycloak URL, but the user hasn’t entered his username anywhere yet, so we don’t have any information to go on.

Please help! We’d hate to conclude that due to a Keycloak limitations, tenant determination is only possible based on application hostname , e.g. https://customer1.oursaas.com not by looking at the username entered by the user, e.g. fred.flintstone@customer1.com.

Application Dialog won’t work with SSO

Before anybody suggests just to add a dialog box in the application asking for the username, determine realm and Keycloak server from that and then redirect to Keycloak using the login_hint GET param from the OIDC spec: That won’t work with SSO, where if the user is signed in applicationA and tries to login to applicationB, she should not be shown any dialog box at all, just be logged in immediately to applicationB. And only the correct Keycloak server knows whether the user is already logged in :frowning:

1 Like

Ok, so are you guys ready for a hack? If you have a better solution, I’d still love to hear about it.

Keep in mind that because of KEYCLOAK-4593 if we have > 100 realms we may need multiple Keycloak servers also.

We’ll need:

  • A separate HTTP server specifically for this purpose, auth-redirector.example.com.
  • An algorithm to determine the Keycloak server and realm from a username (email address).

Here would be the entire OAuth2 Authorization Code Flow:

An application discovers the user wants to log in. Before multiple realms, the Keycloak server and the realm’s name would be a constants, so the application would redirect to:

https://keycloak.example.com/auth/realms/realname/protocol/openid-connect/auth?$get_params

Instead, it redirects to

https://auth-redirector.example.com/?$get_params

auth-redirector determines if it itself has a valid access token for this session, perhaps having to refresh the access token first from the Keycloak server that issued it (the user could have logged out and is trying to login as a different user that is served by a different realm).

If it has an valid access token we can determine the Keycloak server and realm from the username or email address in the access token and redirect to:

https://$keycloak_server/auth/$realm/realname/protocol/openid-connect/auth?$get_params

from here, the OAuth2 Authorization Code Flow proceeds as usual.

Else if it doesn’t have a a valid access token, the auth-redirector stores the original app’s $get_params as session data. It presents a form to the user asking for a username. When the user submits that, we can determine the Keycloak server and realm to use and then auth-redirector itself logs in to the Keycloak server using its own $get_params. Once the auth-redirector gets a call-back, it retrieves the access+refresh token from the Keycloak server and stores them in session data. It then, finally, redirects back to that same keycloak server and realm with the callers original $get_params (from session data). And the OAuth2 Authorization Code Flow proceeds as usual.

This is definitely a hack! But I think it could work. I’d love to try it out some day, time permitting.

Other hacks/solutions are needed for other OAuth2 flows…

This is a cross-post of my Stack Overflow answer

This post includes a discussion of some approaches:

Manual Selection Before Authentication

This is when you have an extra field on your log in form where the user would manually type in their Organization. They might type in “Green” for the Organization that they belong to, and then enter their email and password as usual.

Manual Selection After Authentication

First the user would authenticate using their username and password. Then they land in the lobby of the application where they select their Organization from a list. The user would select the right one and go to the main part of the app.

Automatic Selection by Subdomain

This post will focus on the easiest type of Multi-Tenancy to manage for your users which is Automatic Selection by Subdomain. It’s an advanced Multi-Tenant feature supported by Stormpath. Let’s look at how to build this.

2 Likes

We have a same problem. Our cloud service is multi-tenancy and Keycloak is our IAM of choise. We have created a wrapper application that holds user configurations and specific application permissions. That part of the system has a task to identify a user organization based on email domain, check if exists and make redirect to the correct organization realm where user must insert password, otherwise user login will failed. System must guarantee that user email is unique! In Keycloak user in the same realm must have different emails, not between realms which is the main problem with Keycloak user management, in our use case.

The approach isn’t the simplest one but it works and probably all custom IAM servers inside companies with multy-tenancy cloud services are working in that way. First step is to identify user, second step is to make password validation.

We weren’t happy to develop something that Keycloak should have, which is multi-tenancy login built-in. Anyway, Keycloak is the great piece of software and I’m happy to use it but multi-tenancy is the common scenario this days.
A problem with custom implementation in security context is that are untested.
I hope in the future something will change about that.

1 Like

Keycloak X should offer a scalable implementation for realms - at least on paper:

The current storage layer is complex, especially when deployed to multiple-sites. It has a number of scalability issues like the number of realms and clients.

However I don’t know what is the status on that.
It is supposed to be a release on end of 2020 according to https://www.keycloak.org/2019/10/keycloak-x .

Maybe @stianst or someone else from the community can give an update on Keycloak X status.
Depending on your timeline it might be something to look at.

Thanks for a tip. I didn’t know anything about that.
To be honest the latest versions of Keycloak library, that we have as dependency in our project, expose many ways of doing the same thing. I think that keycloak core team planning some changes of data model on application level. Due to API REST model evolution could be very hard, expecially when you need to keep backword compatibility.

We are in 2020 and we did what was required to have multi-tenancy in our system. If something change soon it will be greate news.