Change user DB connection at runtime in Keycloak user storage SPI - keycloak

We have a single Keycloak realm configured to be used from a different server for authentication. I am implementing Keycloak user storage API to look up from user database. Each server can have a different user database.
I can determine the database to connect based on request details but not sure how can I get the same in user storage API.
Update:
I can also do with identifying client ID.
Another option can be to let the SPI implementation invoke a REST API to validate credentials to simplify keycloak configuration. However this would also need some identifier to determine database.
Please advise.

Keycloak does not provide request URL to user storage SPI implementation. It does provide realm and client IDs. If required, realm or client values can be used to determine required action.

Related

Keycloak: How to log in to another Keycloak instance's realm without redirecting?

We have two Keycloak instances:
Keycloak-A: publicly available
Keycloak-B: available behind a VPN
Both instances can communicate with each other directly without any restrictions.
Is it possible to log in as a user from Keycloak-B using the login form from the public Keycloak-A?By adding Keycloak-B as an IDP on Keycloak-A the login request gets redirected to Keycloak-B.
Exporting the realm from Keycloak-B to Keycloak-A is not an option.
Is it possible to log in using Keycloak-B users without getting redirected on Keycloak-A?
Keycloak allows to set up different User Storages to retrieve user credentials. This is called User Storage Federation. Out of the box Keycloak supports LDAP / Active Directory. If your Keycloak-B already retrieves its user information from some other source like LDAP or AD, it would be easiest to establish a secure connection between Keycloak-A and this other source, too.
If your organisation directly uses Keycloak-B's H2 database to keep all user credentials and for some reason doesn't want to set up an LDAP or similar, I only see the possibility that you try to implement your own custom User Storage SPI for Keycloak-A to retrieve this user data from Keycloak-B.

Flask-OIDC | How to call a specific function after the user logged in

I built a login system using Flask OIDC and Keycloak. In my system, there is some endpoints decorated with oidc.require_login() that calls the Keycloak login page.
My goal is, after the user successfully logged in, my system checks if the user name exists in a specific database.
How can I set a function to be called every time someone successfully logged in with Keycloak and do this verification at the database?
According to your needs there are several ways to create the user in the backend.
The easiest way would be to just check the JWT token on every request. OIDC is based on JWT and that token is available on any request (which should already be done to find user roles etc). So your application can check that JWT and extract the username from it (see here for details about the JWT format). With the username you can check your internal database and create the user, if it doesnt exist. But at that time you'll not have access to any user credentials any more. It is just SSO and you need to trust Keycloak and the JWT... Also - you'll never be informed, if the user will be deleted in Keycloak, which could be an issue.
There is a callback API in Keycloak in form of the Admin URL per client. But the documentation is not clear. It says: It’s used by the Keycloak server to send backend requests to the application for various tasks, like logout users or push revocation policies. But I cannot find a complete list of "tasks". I saw only logout events. see Keycloak documentation and the documentation only talks about that. If I add an admin url to a test client, I did not get any requests at login time.
a different but more complicated way would be to create your own UserStorage SPI in Keycloak. It would be Java of course, but only some classes. There is an HTTP example or have a look at the LDAP user storage SPI, which supports registration too. If you choose that for your realm and a user tries to login to Keycloak (Login form), the SPI can call your backend to check the user. It also could be "used" to create the user in the backend by checking the Keycloak local storage and only if there is a local Keycloak user, call the backend. That isn't the reason, why you should implement the UserStorage SPI, but it's possible. If you think, this is a good idea, I would prefer to use your backend storage as the one and only storage or build a different one, that then could call your real backend in case of a new user. I would use this one by not using Keycloak local stored users but, by using your own database.
next (maybe last one). You can write an EventListener SPI to read all events and only filter the login events, see here and here. I think, that would be the easiest one. But be aware. In that case, the HTTP call to your backend coming from the event itself is based on a normal HTTP request (without OIDC at that time).
The last two examples create a JAR (which is explained in the links). That JAR with the SPI must be deployed in keycloaks standalone/deployments folder. The EventListener should be active by default, the UserStorage SPI must be activated per realm.
But - be aware - Keycloak/SSO/JWT - should not be used by creating users in multiple backends. Syncing the users between all backends in a SSO environment is maybe the wrong way. Most information is located in the JWT or can be called by a backend from one central user identity management. Do not store a user more then once. If you need the user reference in your backend - link just to the username or userid (string) instead of a complete entity.
There is no direct way of doing this, other sotfware like Openam, Okta allow you to trigger specific flows in a post-login configuration.
In keycloak, you can try to create your custom authn flow(using Default Identity Provider, its the only option that allow a redirect), and then select this flow in your Identity provider in post login flow.
The idea here is that after login, the user will be redirected to a link ( an api call that will verify his presence on the external database, and sent him back to keycloak once the verification is done.
More info here

Keycloak - Introspect tokens for different realms using master

We are building an application where multiple clients can register and have their own user bases. For example a client "CompanyA" can register and then allow their users to access to our system with their own usernames (some from LDAP). "CompanyB" can also do the same, the usernames will be unique for one client but can be duplicated across clients.
We are using keycloak for this and have used the concept of realms to achieve this. When a new client registers we create a new realm for them and do the required configurations. This works as expected but we are having issues with our middleware.
Our middleware is Kong which has an OIDC plugin which we integrate with keycloak, however the plugin requires the realm name which in our case is actually going to be dynamic.
For example:
When a user signs in from our UI he receives a token from their client's realm. Now when a user requests a resource from our backend, this request will go through kong.
Kong will introspect this token using its configured client and realm, however this cannot be selected dynamically so ideally I would want to have a configured client on the master realm for each client realm and use this magic client to introspect their tokens.
Is something like this possibel? If not what other avenues can I look into?
You can inspect the Access Token to see from which realm it was created.
If you decode the JWT token with something like https://jwt.io/ you will see a property on the token called issuer. That is the url of the realm that created the token.
So to get the realm, you do something like this:
import org.keycloak.TokenVerifier;
import org.keycloak.representations.AccessToken;
...
AccessToken token = (AccessToken)TokenVerifier.create(tokenString,
AccessToken.class).parse().getToken();
String realm = token.getIssuer().substring(token.getIssuer().lastIndexOf(47) + 1);

How Custom Authentication Works in MongoDB Stitch

Following MongoDB Custom Authentication, it is given that any JWT Token with the minimal below fields works with the MongoDB Stitch Authentication. Also, token needs to be issued by External Authentication System
{
"aud": "<stitch app id>"
"sub": "<unique user id>",
"exp": <NumericDate>,
}
I've tested this and it works as well
Created Stitch App and enabled Users with Custom Authentication Provider
Generated Sample Token through Jwt.io with the below inputs. (Use same algorithm and key as configured when enabling Custom Authentication Provider, Here it is, HS256 and the )
It works in the way,
It is validating the users in MongoDB Stich Users Collection with the unique value provided in sub: "sub": "<unique user id>" and if the user is present then it returning the Object Id for that User.
If the user is not present then it is creating one against the input and returning the Object Id.
Queries are,
Why it is creating a new user instead of returning login failure, which in turn works like any user can log in with any credentials on demand?
If MongoDB Stitch Custom Authentication involves External Authentication System to issue JWT, where the user data will be actually stored when user registration? - MongoDB Stitch App Collection or External Authentication API System?
Here is the response from MongoDB Support
Why is Stitch creating a new "User"
The "User" Stitch creates in this scenario is an internal user. This "user" also contains the user data and metadata provided from the JWT and is not stored alongside your other collections in the Atlas cluster your application is linked against. Note that this "user" is not accessible to MongoDB without using a trigger or other function to load it into the database.
Why isn't a login failure returned
A login failure is not being returned because the custom authentication provider is only checking the signed JWT from the external system against its own copy of the signing key. If the signatures match then the login is deemed successful.
It is the responsibility of the external authentication provider to fail the login; not Stitch.
Where will the user data actually be stored
The user data should be managed within your database. The most efficient way to integrate this with the Custom Authentication provider is to use an Authentication Trigger on Create and/or Login operation types. This would allow you to run a Stitch Function any time an authentication event is triggered.
There is an example of using authentication triggers on the MongoDB blog which may help explain the process further.

keycloak - realm resolution based on username (email address)

I'm working on a multi tenant project where usernames are actually their email addresses and the domain of the email serves as a tenant identifier.
Now in keycloak I'll have different realms per tenant, but I want to have a single login page for all tenants and the actual realm that will do the authentication to be somehow resolved by the username (email address).
How do I go about doing that?
I found a thread on the mailing list (that I cant find now...) that discussed the same problem. It was something along the lines of - create a main realm that will "proxy" to the others, but I'm not quite sure how to do that.
I think Michał Łazowik's answer is on the right track, but for Single-Sign-On to work, it needs to be extended a little.
Keep in mind that because of KEYCLOAK-4593 if we have > 100 realms we may have to have 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 realm's name would be a constant, 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...
The idea from the mailing list is to write a service (let's say auth-redirector.example.com) that has a single input field for email, finds realm based on domain and redirects to that realm's keycloak endpoint (e.g. auth.example.com/auth/realms/realm-name/etc…) while keeping all GET params.
You can find examples of direct login/registration URLs here: https://lists.jboss.org/pipermail/keycloak-user/2016-July/007045.html
One usability problem is that users would have to provide their email twice, I have not yet found a way to pass the username via the login URL.