Token exchange (external to internal) and mapping don't work together

Hi,
I’m trying to get external to internal to work.

My situation is quite basic, I’m starting with 2 Keycloaks working together, KC-IDP and KC2.
KC2 should not need any users, it uses KC-IDP as an identity provider.
It’s there to provide JWT to control access to some services, the services don’t want to know about KC-IdP.
Anyone wants to call a service, they get a token from KC-IDP, and then use it to get a token to KC2 and use that one to call the service.

KC-IDP has some users and some attributes and roles that makes it fine into the claims, it has 2 clients, one called “login”, and one called “kc2”, they have mappers for the JWT.
KC2 defines some roles, realm and client. The client is “dest”, cos it’s the destination client.

My aim is to start with a token from KC-IDP/“login”, and end with a token from KC2/“dest”.
All using curl and REST. :slight_smile:

First what works, then what doesn’t.
This works, using only Keycloak UIs:

  • define all the stuff above.
  • define an Identity Provider (IdP) in KC2 (of the type Keycloak OIDC)
  • define mappings in the IdP
    ** from claim to attribute
    ** from claim to role
  • log in to KC2, get redirected to KC-IDP (yay!), go back to KC2, login successful (yay!).
  • user was created in KC2 (yay!)
  • user has attribute (yay!)
  • user has role (yay!)

This looks good, and actually I thought I had made it. The login goes to KC-IDP with the appropriate client, IdP mappers work fine, I can even define a realm-admin with the admin role, so basically I don’t need any user in KC2 any more.

This also works, using curl REST:

  • get a token from KC-IDP/“login” client
  • exchange it inside KC-IDP (internal exchange) from “login” to “kc2” clients (yay)
  • call KC2 to exchange it for a token from KC2/“dest” (this took me some time but now it’s ok)

Again, I thought this was the end of it. I actually managed to exchange external to internal. I hop from one KC client to another, just like I wanted. Users are created on the fly.

What doesn’t work:

  • the mapping from claim to role in the IdP does not work. The final token (KC2/“dest”) does not have any role.
  • the mapping from claim to attribute does not work. However… it works the second time around: this time the user already exists, it is already linked to the IdP, and the user ends up with some attributes, who end up in the JWT.

So I end with an unusable token, because it has no claims to anything in KC2. :frowning:
(except for some attributes if I run it all twice)

I have tried peeking into the code, to no avail. I have a feeling there’s something I missing.
I checked and there are sessions created for my users when I do the token exchange.
BTW, the client mappers behave exactly as expected, they return the proper JWT given the attributes and roles that the user has.

Any ideas?

Has anyone succeeded in getting internal to external to work with mapping using tokens (not login)?

thanks

So for the attribute mapping I have found the bug
[KEYCLOAK-17793] Attribute mapping not done at first broker login during idtoken/jwt exchange - Red Hat Issue Tracker

this is precisely what I’m getting.

For role exchange there’s a bug ( KEYCLOAK-15772) but it’s not the same one, I don’t actually get a NullPointerException.

However as these two bugs are open, for me token exchange (not login using redirection) is unusable. No mapping means no authorization is possible. :frowning:

I have a scenario similar to yours.

My root level keycloak (ROOT) talks to a identity provider (IDP-EXT) (which has strict client registration process, so I have only this keycloak talking to it). I map the attributes and claims accordingly so the ROOT token seems just like the IDP-EXT token.

I have several other applications belonging to several costumers. For each costumer, I have a keycloak (TENANT-KEYCLOAK) with user’s groups and roles.

Everytime a costumer has an application need to authenticate a user using the IDP-EXT button, I set ROOT as an identity provider in TENANT-KEYCLOAK with the display name “Login with IDP-EXT”. I map the claims and attributes accordingly to get claims from the ROOT token (thus IDP-EXT token) that make sense to the application.

That way, the costumer’s applications are not coupled with neither the ROOT or the IDP-EXT, they don’t even know they exist.

Users just see the “Login with IDP-EXT” on their normal login screen (provided by TENANT-KEYCLOAK) and get the option to authenticate against it.

In my scenario, the application doesn’t call any external service which needs the IDP-EXT token, it’s just user authentication, but if I had that necessity, it’s possible (and that will cause strong coupling) to get that token using the broker token endpoint in two steps, first in the TENANT-KEYCLOAK, to obtain ROOT token, then in the ROOT, to obtain IDP-EXT token.

1 Like

Good to see that others are using this, thanks.

But you don’t have the same bug, right?
Because the bug does not appear when logging in, only when using REST calls to get tokens. As I said, login works fine. (probably because it’s at the heart of the product, contrary to token exchange which is a preview feature)

If I understand correctly, your ROOT mainly exists to host the clients corresponding to the tenancies. So, the IDP-EXT only knows that it serves one unique client, which makes it easier to configure once (may save some cost depending on what it is :wink: ).

Just a remark: when you say several Keycloaks, it could be several realms in the same keycloak, right?

and now I have the answer for the role.

https://issues.redhat.com/browse/KEYCLOAK-19418

TL;DR: role claims need to be in user info.

So the workaround here is to modify KC-IDP so that it not only puts roles in the JWT claims, but also returns them from user info endpoint. That’s a client mapper option.
I did that by creating a second ClientScope next to “roles”, called “roles-user-info”, with the same 2 role mappers as “roles” but the checkbox “user info” checked.

I don’t like this because it means modifying the IDP, which should be as open as possible… that’s the part that would be replaced with any existing IDP.

Yes, you are right. My comment was under the assumption that you can use the broker token endpoint as a workaround to the token exchange bugs.

I have several. It’s a tenant isolation requirement.

Sorry for raising this old subject.

I’m trying to use Token exchange and reached the same point as you, @jrobinss: the token resulting of the exchange does not contain any KC-IDP roles. I have added the “roles-user-info” scope and roles are now exposed in the userinfo response (I have the same result with a scope dedicated to my client). However, roles are not present in the token.
Actually, what I would be able to do is to map a KC-IDP role to a KC2 role. For instance, for any user having the role “admin” in KC-IDP (role being returned by /userinfo), I would like to have, in the resulting token, the role “manager” (KC2 role).

Any help would greatly appreciated :pray:.

Sorry, I can’t really help. I’m not working on that any more and I can’t add to what I presented above.

From what I wrote, I actually didn’t have that problem: once KC2 got the role from the info endpoint, then I guess I managed to put it in the final token. Is there some JWT mapper that you are missing in KC2 maybe?

I have tried to check out the bug that I referenced, but Keycloak has changed their bug reporting so it doesn’t work any more. There’s this related one, that appears to have been fixed: UserInfo: Role name mapper is not respected for user info endpoint · Issue #15624 · keycloak/keycloak · GitHub

I also found that, I’m not sue whether it’s related: Keycloak - Missing data in the userinfo response - Getting advice - Keycloak

cheers

Thank you @jrobinss for the kind reply!

Is there some JWT mapper that you are missing in KC2 maybe?

I probably need a mapper somewhere in KC2 but I 'm very new to Keycloak and don’t know which one to use.
The roles mappers I can see (Role Name Mapper, User Realm Role, User Client Role) allow me to map a role that is known by the broker (KC2). Not a role coming from the KC-IDP (userinfo).
I even tried with the User Attribute mapper and it didn’t work either (I guess a claim in /userinfo is not a user attribute).
I wonder how to consider a claim of the /userinfo endpoint in the context of Token Exchange and how to use it with a mapper.