I have a spring boot application secured with Keycloak 11.0.2, and my Keycloak setup is as follows:
Central
with a role CentralWebUser
and a client SpringWeb
. The client has
Access Type
: public
and only one flow enabled, namely Standard Flow Enabled
Valid Redirect URIs
: http://localhost:8000/*SpringApp
with a role WebUser
and a client spring_brokering
springuser
with the realm role WebUser
spring_brokering
has only the Standard Flow Enabled
set to ON, Valid Redirect URIs
: http://localhost:8080/*, and Access Type
: Confidential
The second realm is an IDP of the first. So to login a user goes to the Central
login page and selects the IDP SpringAppIDP
.
The IDP configuration is as follows:
alias
: SpringAppIDP
, with everything else being OFF
except for the option enabledSpringApp
endpoints (e.g.,
Authorization URL
: http://127.0.0.1:8080/auth/realms/SpringApp/protocol/openid-connect/auth)spring_brokering
and its secret, respectively.On the Spring side, I have the following properties worth mentioning:
server.port = 8000
keycloak.realm = Central
keycloak.auth-server-url = http://localhost:8080/auth
keycloak.ssl-required = external
keycloak.resource = SpringWeb
keycloak.public-client=true
keycloak.security-constraints[0].authRoles[0]=WebUser
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/services/*
When I access http://127.0.0.1:8080/services
I got redirected to the Keycloak Central
Realm Login page, then I click the SpringAppIDP
and enter the username springuser
and its password. The login is successful, but I got an access denied, which means that the user springuser
does not have the role WebUser
. However, that role was assigned to that user within the second realm (i.e., SpringApp
).
Interestingly, if in the first Realm I create an identity provider Mapper External Role to Role
(in the IDP SpringAppIDP
configuration) mapping the external role of WebUser
to the CentralWebUser
and change the spring properties to :
keycloak.security-constraints[0].authRoles[0]=CentralWebUser
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/services/*
I am able to login, which means that Keycloak knew that the user had the WebUser
role, hence mapping that role to the CentralWebUser
role.
I would like to know if it is possible to explicitly import the roles from an external IDP into an internal one? Or if (and how) can I request a token on behalf of the user that would have that users' roles from both the Central
and SpringWeb
Realm in the that token, without having to explicitly create a Role Mapper for each user role.
I would like to know if it is possible to explicitly import the roles from an external IDP into an internal one? Or if (and how) can I request a token in behalf to the user that would have that users' roles from both the Central and SpringWeb Realm in the that token, without having to explicitly creating a Role Mapper for each user role.
Without explicitly creating a Role Mapper for each user role, the only solution that I have found was to extend the Keycloak code; which comes with obvious downsides.
In retrospect, it actually makes sense that Keycloak does not offer out-of-the-box a way of automatically importing all the roles from the external IDP. For instance, if I am using Google as an external IDP why should my internal IDP (i.e., Keycloak) care about the exact name of the roles used by Google?!. Most-likely, either those roles are meaningless for the internal IDP, and when they are meaningful they might have a different name. Regardless, for those exceptions, one can use the Role Mapper feature.
Nevertheless, to automatize a bit the process, I have created a file that maps the roles of the internal IDP to external IDP, for instance:
ROLE A | ROLE B
....
I also have a JSON file with a template of a Role Mapper example with some tags to be replaced afterwards (e.g., the fields role
and external.role
).
With scripts, I read the file that has the mapping between roles, and use Keycloak Admin REST API to create the roles, the mappers and so on.
The logic that I have used is as follows:
POST /{realm}/roles
POST /{realm}/identity-provider/instances/{alias}/mappers
with the content of the JSON
template Role Mapper file (with its tags replaced, accordingly).The rationale for not creating the Realm Role in the external IDP is that all the roles from the external IDP should have been loaded already from the LDAP anyway. For the internal IDP, I do create because it is expectable that for mappings 1 to 1 that the roles from the LDAP (loaded into the external IDP) are not yet created on the internal IDP.