I came up with this situation where I had to set permissions for every API within my application and associate a role for every user in the application. This user role should contain the combination of permissions that were set to the APIs.
In Keycloak there is no separate thing called permission. All the permissions are treated as roles. But there is a way to create sub-roles within a role. So I created API permissions as sub-roles and associated them with the user roles.
We’ll discuss how to create roles, associate them with nested roles, and assign these roles to the user. I will create the role using API. For this, we need the access token. We can get the access token using the client credentials grant method below
User client credentials are the recommended way to fetch the access token.
Client Credentials grant type allows us to request an admin access token by providing a client ID and client secret instead of an admin username and password. To use this approach, the client called “admin-cli” which is in the “master” Keycloak realm needs to be set to “confidential“.
To change the “admin-cli” client Access Type property from Public to Confidential, and save the settings. You will need to log in to “master” Realm, switch to the OAuth 2 “Clients” list, and edit the “admin-cli“. Change the Access Type property from Public to Confidential and make sure that the “Service Accounts Enabled” option is turned on. Once you are done making the above changes, click on the Save button. The page will reload and at the top, you will see a new tab called “Credentials“. Click on the Credentials tab and copy the value of client_secret.
Now when you have the client secret value for OAuth 2 Client “admin-cli“, you can request an admin access token using the “client-credentials” grant type
curl --location --request POST 'http://localhost:8080/auth/realms/master/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=admin-cli' \
--data-urlencode 'client_secret=7fb49e15-2a86-4b7c-a648-277'-
If the request is successful, in response you should get an access token.
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2d1pGSUtKWVgwWnZRVmhtV0FLZ1JLZkZvbldzRk4tdjltT09QYlJ5Zjg4In0.eyJleHAiOjE2NzYyMTEyMzQsImlhdCI6MTY3NjIxMDkzNCwianRpIjoiNjI5MmQ5YTQtYmVmMy00ZTkzLWFmNDYtOTIyMmZmOWRiYmU5IiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5pcXpzeXN0ZW1zLmlvL3JlYWxtcy9mb3Jtcy1uZXh0LXN0YWdpbmciLCJzdWIiOiIxYWNmMGMwNC03YjU2LTRhY2YtYmVhMC04NDBiOWE0MjYwMWMiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJhY3IiOiIxIiwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJjbGllbnRIb3N0IjoiMTAuMTQyLjE1LjIwOSIsImNsaWVudElkIjoiYWRtaW4tY2xpIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LWFkbWluLWNsaSIsImNsaWVudEFkZHJlc3MiOiIxMC4xNDIuMTUuMjA5In0.EJWb-pOSS7GQOjBqbrUpTuBn66M90EmTcomg__6Yeo63BgA7bi2vr9zZgrbSXQZRsOqwNDnEdYIa8TyYXSKuTyIUCUxDK13SXcu4HAl2lhfT93rpqqU6cYV0Zhbs_Q27dJIk6myA6L7FcHF7G1VEBfb8PeDIAFO5PjifcaAfMi6ICYHqhKWoyoZF5zPCjQYtJqg7VO3TV_wL4C4qxH7BtcsjBQNjH431UD-J9LqRR244gHQjUwhGjuiHie2mo2EtdCUDi0ps8i1EdGxjAB7-bD33l2Bmr0zgnRaVUnflA5nlcJDiX5hWR62LeDZnSNxp7OrJNIp-nJqJuO-gf5vb_Q",
"expires_in": 300,
"refresh_expires_in": 0,
"token_type": "Bearer",
"not-before-policy": 0,
"scope": "profile email"
}
Create Role
Roles can be either Realm level or client level. The realm-level roles can be applied to users in any client within the realm. The client-level roles are restricted to the client's scope.
Here, we can create roles within the client. The role can either be a base role or a sub-role. Keycloak treats all these roles as common roles.
To create roles within the client we need the id of the client. The id of the client is not the same as the client's id. First, we have to fetch the client ID.
The request body to fetch the client ID would look like this,
curl - location - request GET 'http://localhost:8080/auth/admin/realms/test/clients?clientId=demo_app' \
- header 'Authorization: Bearer oTBKKZwFgRmW/0xN1SvF/rua=EiTnXzIf30qVZ4zdM38d-kQUS0!7q7Oq6uLKqZZ4!KO3GCEd8gbFPNDqXa3rs=UMyIMrkUTE0mdXnq3PR7nJ2/kAnRlly5N5QCqnlnZ4XeukT31qcesJ2N2wbSGL1x/ljrVyVRyL7=hfA3GqE/C6AngvnEsz38Pl=0KIO8jXpoydLtBGdC6JAjVeMf8agTZ082SfPcpJ2j6FRM4MAZJlR3CKQAoazfU6-orKE?B' \
- header 'Content-Type: application/json' \
On successful request, the client ID would be returned as a response. The client ID will be a UUID.
We’ll start by creating a role called member which will act as a user role and a role called read-access which will act as API permission.
Endpoint:
POST /{realm}/clients/{id}/roles
The request body for member roles would be like this,
curl - location - request POST 'http://localhost:8080/auth/admin/realms/test/clients/9f1da13c-5049-43c4-9d17-113d9a4726bf/roles' \
--header 'Authorization: Bearer oTBKKZwFgRmW/0xN1SvF/rua=EiTnXzIf30qVZ4zdM38d-kQUS0!7q7Oq6uLKqZZ4!KO3GCEd8gbFPNDqXa3rs=UMyIMrkUTE0mdXnq3PR7nJ2/kAnRlly5N5QCqnlnZ4XeukT31qcesJ2N2wbSGL1x/ljrVyVRyL7=hfA3GqE/C6AngvnEsz38Pl=0KIO8jXpoydLtBGdC6JAjVeMf8agTZ082SfPcpJ2j6FRM4MAZJlR3CKQAoazfU6-orKE?B' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "member",
"description": "member is a basic level permission",
"composite": false
}'
On successful request, the response will be 201 Created.
The request body for the read-access role would be like this,
curl - location - request POST 'http://localhost:8080/auth/admin/realms/test/clients/9f1da13c-5049-43c4-9d17-113d9a4726bf/roles' \
--header 'Authorization: Bearer oTBKKZwFgRmW/0xN1SvF/rua=EiTnXzIf30qVZ4zdM38d-kQUS0!7q7Oq6uLKqZZ4!KO3GCEd8gbFPNDqXa3rs=UMyIMrkUTE0mdXnq3PR7nJ2/kAnRlly5N5QCqnlnZ4XeukT31qcesJ2N2wbSGL1x/ljrVyVRyL7=hfA3GqE/C6AngvnEsz38Pl=0KIO8jXpoydLtBGdC6JAjVeMf8agTZ082SfPcpJ2j6FRM4MAZJlR3CKQAoazfU6-orKE?B' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "read-access",
"description": "read-access is a api level permission",
"composite": false
}'
On successful request, the response will be 201 Created.
Here, both roles have the composite parameter as false by default, which means both roles are at the root level (base roles).
Now we have to add read-access as a sub-role to the member role. This is called nested role mapping. For this, we have to update the member role with an updated request body as,
curl - location - request POST 'http://localhost:8080/auth/admin/realms/test/clients/9f1da13c-5049-43c4-9d17-113d9a4726bf/roles/member/composites' \
--header 'Authorization: Bearer oTBKKZwFgRmW/0xN1SvF/rua=EiTnXzIf30qVZ4zdM38d-kQUS0!7q7Oq6uLKqZZ4!KO3GCEd8gbFPNDqXa3rs=UMyIMrkUTE0mdXnq3PR7nJ2/kAnRlly5N5QCqnlnZ4XeukT31qcesJ2N2wbSGL1x/ljrVyVRyL7=hfA3GqE/C6AngvnEsz38Pl=0KIO8jXpoydLtBGdC6JAjVeMf8agTZ082SfPcpJ2j6FRM4MAZJlR3CKQAoazfU6-orKE?B' \
--header 'Content-Type: application/json' \
--data-raw '{
"composite": true,
"composites": [
{
"id": "c44ccde3-8585-4dfb-8a17-11b6c4ffee5d",
"name": "read-access"
}
]
}'
On successful request, nested roles will be created i.e.) member will be the base role that has read-access permission.
Assign roles to the user
For implementing the Role-based access control method, we have to assign the roles to the user.
The request body will be like this,
curl - location - request POST 'http://localhost:8080/auth/admin/realms/test/users/c44ccde3-8585-4dfb-8a17-11b6c4ffee5d/role-mappings/clients/9f1da13c-5049-43c4-9d17-113d9a4726bf' \
--header 'Authorization: Bearer oTBKKZwFgRmW/0xN1SvF/rua=EiTnXzIf30qVZ4zdM38d-kQUS0!7q7Oq6uLKqZZ4!KO3GCEd8gbFPNDqXa3rs=UMyIMrkUTE0mdXnq3PR7nJ2/kAnRlly5N5QCqnlnZ4XeukT31qcesJ2N2wbSGL1x/ljrVyVRyL7=hfA3GqE/C6AngvnEsz38Pl=0KIO8jXpoydLtBGdC6JAjVeMf8agTZ082SfPcpJ2j6FRM4MAZJlR3CKQAoazfU6-orKE?B' \
--header 'Content-Type: application/json' \
--data-raw '[
{
"id": "c44ccde3-8585-4dfb-8a17-11b6c4ffasw",
"name": "member"
}
]'
On successful request, the user will be assigned the member role.
Fetch roles associated with the user
There are two ways to get the roles associated with the user. We can either decode the access token or use the API to fetch the roles.
Let us decode the access token to fetch roles here,
1wbGF0ZXM6Y3JlYXRlIiwic3Vic2NyaXB0aW9uOmNhbmNlbCIsImlxei1zeXN0ZW1zJHN1cGVyLWFkbWluIiwidXNlcjpkZWxldGUiLCIjZm9ybXM6cHVibGlzaC5zZWxmIiwiI2Zvcm1zOnB1Ymxpc2giLCJyb2xlOnJlYWQiLCIjdXNlcjp1cGRhdGUuc2VsZiIsIiN1c2VyOnVwZGF0ZSIsInN1YnNjcmlwdGlvbjp1cGRhdGUiLCJmb3Jtczp1cGRhdGUiLCJzdWJzY3JpcHRpb246Y3JlYXRlIiwicm9sZTpjcmVhdGUiLCJ0ZW1wbGF0ZXM6cmVhZCIsInBheW1lbnRzOnJlYWQiLCIjZm9ybXM6dXBkYXRlLnNlbGYiLCJwcm9mLWNvbXBhbnkkbWVtYmVyIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCBVc2VyX1JlYWxtX1JvbGVfTWFwcGVyIHJvbGVzIiwic2lkIjoiYjI5ZmNiNWMtZmU3MC00NTVjLTk3MTktNDg0NzcyOTI0OGY1IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJTYW50aGl5YSBHdW5hIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2FudGhpeWEuZ0BpcXpzeXN0ZW1zLmNvbSIsImdpdmVuX25hbWUiOiJTYW50aGl5YSIsImZhbWlseV9uYW1lIjoiR3VuYSIsImVtYWlsIjoic2FudGhpeWEuZ0BpcXpzeXN0ZW1zLmNvbSJ9
The decoded token looks like this. The roles list is available below the resource_access
"iat": 1674584331,
"auth_time": 1674580691,
"jti": "e77bd9e8-3148-49f0-8fdb-8a9277defc19",
"iss": "http://localhost:8080/auth/admin/realms/test",
"sub": "24199ac7-3148-49f0-8fdb-a255f230dc2f",
"typ": "Bearer",
"azp": "test",
"nonce": "ac12440d-26b9-4eba-8bd7-c6d088a1ee38",
"session_state": "b29fcb5c-fe70-455c-9719-4847729248f5",
"acr": "0",
"allowed-origins": [
"*"
],
"resource_access": {
"test": {
"roles": [
"read-access",
"write-access",
"member"
]
}
}
}
Using keycloak API,
To fetch the role associated with the user, the request body would look like this.
curl - location - request GET 'http://localhost:8080/auth/admin/realms/test/users/c44ccde3-8585-4dfb-8a17-11b6c4ffee5d/role-mappings/clients/9f1da13c-5049-43c4-9d17-113d9a4726bf' \
--header 'Authorization: Bearer oTBKKZwFgRmW/0xN1SvF/rua=EiTnXzIf30qVZ4zdM38d-kQUS0!7q7Oq6uLKqZZ4!KO3GCEd8gbFPNDqXa3rs=UMyIMrkUTE0mdXnq3PR7nJ2/kAnRlly5N5QCqnlnZ4XeukT31qcesJ2N2wbSGL1x/ljrVyVRyL7=hfA3GqE/C6AngvnEsz38Pl=0KIO8jXpoydLtBGdC6JAjVeMf8agTZ082SfPcpJ2j6FRM4MAZJlR3CKQAoazfU6-orKE?B' \
--header 'Content-Type: application/json' \
To fetch the composites associated with each role, the request body would look like this.
curl - location - request GET 'http://localhost:8080/auth/admin/realms/test/clients/9f1da13c-5049-43c4-9d17-113d9a4726bf/roles/member/composites' \
--header 'Authorization: Bearer oTBKKZwFgRmW/0xN1SvF/rua=EiTnXzIf30qVZ4zdM38d-kQUS0!7q7Oq6uLKqZZ4!KO3GCEd8gbFPNDqXa3rs=UMyIMrkUTE0mdXnq3PR7nJ2/kAnRlly5N5QCqnlnZ4XeukT31qcesJ2N2wbSGL1x/ljrVyVRyL7=hfA3GqE/C6AngvnEsz38Pl=0KIO8jXpoydLtBGdC6JAjVeMf8agTZ082SfPcpJ2j6FRM4MAZJlR3CKQAoazfU6-orKE?B' \
--header 'Content-Type: application/json' \
The response for getting the roles would be,
[
{
"id": "df9aaf7e-b54f-4439-9265-ace2e82561ad",
"name": "member",
"description": "member is a base role",
"composite": true
},
{
"id": "88b12a77-31e8-452e-a39b-5c7d1901a3e0",
"name": "read-access",
"description": "read permission",
"composite": false
},
{
"id": "ee43504a-8173-415a-a64b-fe44b97364b7",
"name": "write-access",
"description": "write permission",
"composite": false
}
]
We have come to the end of this blog. Now, we can create a role, create a nested role within that, associate the role to the user, and fetch the roles associated either using a token or admin API. This is the basic introduction to user role management in any application.
Top comments (0)