UMA 2.0 is known as a delegation of authorizations standard but could be sometimes tricky and unclear. Keycloak is fully compatible with UMA 2.0. With a tool developped by our partner please-open.it, let’s see how to use Keycloak and UMA 2.0 with bash. This article explains what is UMA 2.0 with an example using Please Open It new bash tool : uma-bash-client.sh
What is UMA 2.0 ?
UMA 2.0, defined as « User-Managed Access », is an extension of oauth2. It has the ability of distinguishing the owner and the requester. Oauth2 does not have it. So it allows new features such as :
- an owner can share a document with other users
- one server can manage many resources from differents domains/applications
- some global rules around resources management could be defined
UMA 2.0 comes with some standard endpoints and APIs just like Oauth2/OpenId connect. In Keycloak, we have the endpoint located at
.well-known/uma2-configuration
In Keycloak, UMA 2.0 is still a preview feature, so Keycloak must be launched with the -Dkeycloak.profile=preview flag.
https://www.keycloak.org/docs/latest/authorizationservices/#service_overview
Start with a Keycloak Realm
You can subscribe for a realm on our Keycloak as a service infrastructure on https://realms.please-open.it. We have all preview features enabled on the new Keycloak.X distribution (https://blog.please-open.it/keycloak-x/).
By default, UMA 2.0 is disabled for users to manage their resources in the account console.
First of all, create a confidential client with authorizations enabled.
It shows a new tab « authorizations » that we will take a look at later.
Create some users, and assign the role « uma_protection » from the client created previously.
Reminder : login with oauth2 using « password flow » (direct access grant)
From the tool : https://github.com/please-openit/oidc-bash-client
You can authenticate a user with the « password » flow, described as « resource owner password grant » in Keycloak.
With the openid configuration endpoint, you can enter :
./oidc-bash.sh --operation resource_owner_password_grant \
--open-id-endpoint KEYCLOAK_URL \
--client-id uma-client \
--client-secret XXXX-XXXX-XX-XXXXX \
--username alice\
--password alice\
--field .access_token
this will return an access_token.
This access_token will be used in many requests.
uma2-bash-client.sh
Only clone the repository https://github.com/please-openit/uma2-bash-client.
You have to install curl and jq if you do not have it yet.
This script is a wrapper of some UMA 2.0 APIs (standard and some specific to Keycloak) that we will use in this post.
Resources management
https://www.keycloak.org/docs/latest/authorization_services/#_service_protection_resources_api
If users can manage their own resources, we can use those APIs with an access_token from a user. If not, use an admin token to create resources managed by policies.
./uma2-bash-client.sh --operation create_resource\
--uma2-configuration-endpoint https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc/.well-known/uma2-configuration\
--access-token $ACCESS_TOKEN\
--resource-name myresource\
--resource-owner alice\
--resource-type https://please-open.it\
--resource-scopes "[\"read\", \"write\"]"
Also, this resource can be managed with :
- update_resource
- delete_resource
- get_resource
- list_resources
Get the previously created resource :
./uma2-bash-client.sh --operation get_resource\
--uma2-configuration-endpoint https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc/.well-known/uma2-configuration\
--resource 847016ce-bd6f-4ee0-873b-64ebbfc0888f\
--access-token $ACCESS_TOKEN
returns :
{
"name": "myresource",
"type": "https://please-open.it",
"owner": {
"id": "2f6dddbf-83d3-400b-b9d1-6cc36eb4d11e"
},
"ownerManagedAccess": true,
"attributes": {},
"_id": "847016ce-bd6f-4ee0-873b-64ebbfc0888f",
"uris": [],
"resource_scopes": [
{
"name": "read"
},
{
"name": "write"
}
],
"scopes": [
{
"name": "read"
},
{
"name": "write"
}
]
}
from Alice’s account console we now have the resource created for her.
Sharing process could be done using the console.
With the console (and API), we need the user id. Here how to share the resource only with the scope read to John :
./uma2-bash-client.sh --operation share_access\
--uma2-configuration-endpoint https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc/.well-known/uma2-configuration\
--resource 847016ce-bd6f-4ee0-873b-64ebbfc0888f\
--requester 5b9ee035-1e59-4c49-8ddd-d03c08f30949\
--granted true --scope read\
--access-token $ACCESS_TOKEN
Now the resource is shared with John (and approved with the argument « granted » at « true »)
The reverse operation is called revoke_access for removing this one :
./uma2-bash-client.sh --operation revoke_access\
--uma2-configuration-endpoint https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc/.well-known/uma2-configuration\
--resource 847016ce-bd6f-4ee0-873b-64ebbfc0888f\
--requester 5b9ee035-1e59-4c49-8ddd-d03c08f30949\
--scope read\
--access-token $ACCESS_TOKEN
UMA tokens
from : https://www.riskinsight-wavestone.com/2019/03/demystifions-ensemble-uma2-0/
Get an authorization on a resource
./uma2-bash-client.sh --operation get_authorization_resource\
--uma2-configuration-endpoint https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc/.well-known/uma2-configuration\
--resource 847016ce-bd6f-4ee0-873b-64ebbfc0888f\
--scope read\
--audience uma-client\
--access-token $ACCESS_TOKEN
this operation uses the /token endpoint from oauth2
2 tokens are created : access_token and refresh_token. Take a look at both of them.
Access_token :
{
"exp": 1628836615,
"iat": 1628836315,
"jti": "3f01bf5b-92ff-4d81-9f2a-37924077e4ef",
"iss": "https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc",
"aud": "uma-client",
"sub": "2f6dddbf-83d3-400b-b9d1-6cc36eb4d11e",
"typ": "Bearer",
"azp": "uma-client",
"session_state": "9948070f-740f-4b05-b36d-4e9f5f9a7fdf",
"acr": "1",
"allowed-origins": [
"http://127.0.0.1"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization",
"default-roles-5ae55f12-1515-47c8-9678-c740b0c852fc"
]
},
"resource_access": {
"uma-client": {
"roles": [
"uma_protection"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"authorization": {
"permissions": [
{
"scopes": [
"read"
],
"rsid": "847016ce-bd6f-4ee0-873b-64ebbfc0888f",
"rsname": "myresource"
}
]
},
"scope": "profile email",
"email_verified": true,
"preferred_username": "alice"
}
Refresh_token :
{
"exp": 1628838115,
"iat": 1628836315,
"jti": "6c82ecda-47ed-4da2-9e15-41e7d689b5e8",
"iss": "https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc",
"aud": "https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc",
"sub": "2f6dddbf-83d3-400b-b9d1-6cc36eb4d11e",
"typ": "Refresh",
"azp": "uma-client",
"session_state": "9948070f-740f-4b05-b36d-4e9f5f9a7fdf",
"authorization": {
"permissions": [
{
"scopes": [
"read"
],
"rsid": "847016ce-bd6f-4ee0-873b-64ebbfc0888f",
"rsname": "myresource"
}
]
},
"scope": "profile email"
}
Both of those tokens are created because the user Alice has access to the resource. Of course, because John has access to the resource with the read scope, we can create those tokens with a valid access_token from John.
With the scope « write », it will return an error because John does not have access to the resource with the « write » scope.
./uma2-bash-client.sh --operation get_authorization_resource\
--uma2-configuration-endpoint https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc/.well-known/uma2-configuration\
--resource 847016ce-bd6f-4ee0-873b-64ebbfc0888f\
--scope write\
--audience uma-client\
--access-token $ACCESS_TOKEN
{
"error": "access_denied",
"error_description": "not_authorized"
}
The returned access_token is called a Requesting Party Token, the refresh_token is called Persisted Claims Token.
Tickets please !
This is called « permissions » in UMA, with a specific endpoint dedicated to.
A ticket is a token representing a permission request.
https://www.keycloak.org/docs/latest/authorization_services/#_overview_terminology_permission_ticket
https://www.keycloak.org/docs/latest/authorization_services/#_service_protection_permission_api_papi
As John, I can request a permission ticket for the resource myresource with the scope write
./uma2-bash-client.sh --operation create_permission_ticket\
--uma2-configuration-endpoint https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc/.well-known/uma2-configuration\
--resource 847016ce-bd6f-4ee0-873b-64ebbfc0888f\
--resource-scopes "[\"write\"]"\
--access-token $ACCESS_TOKEN
I got this ticket :
{
"ticket": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmZjc0Y2MyNS0zYWQyLTRkZGYtYWFhNC0yZTkyZDg1NjI4YTkifQ.eyJleHAiOjE2Mjg4MzgyMTMsIm5iZiI6MCwiaWF0IjoxNjI4ODM3OTEzLCJwZXJtaXNzaW9ucyI6W3sic2NvcGVzIjpbIndyaXRlIl0sInJzaWQiOiI4NDcwMTZjZS1iZDZmLTRlZTAtODczYi02NGViYmZjMDg4OGYifV0sImp0aSI6ImRhZDA1YzZlLWU1MTItNDBiNC05YjBiLTczNTA1ZTBiODEyZS0xNjI4ODM4MDI4MDk5IiwiYXVkIjoiaHR0cHM6Ly9hcHAucGxlYXNlLW9wZW4uaXQvYXV0aC9yZWFsbXMvNWFlNTVmMTItMTUxNS00N2M4LTk2NzgtYzc0MGIwYzg1MmZjIiwic3ViIjoiNWI5ZWUwMzUtMWU1OS00YzQ5LThkZGQtZDAzYzA4ZjMwOTQ5IiwiYXpwIjoidW1hLWNsaWVudCJ9.SnQ52q94VWWBOBB-jdCzl8pWffEuwZJf9HnWQ-ctJcw"
}
Let’s explore this one :
{
"exp": 1628838213,
"nbf": 0,
"iat": 1628837913,
"permissions": [
{
"scopes": [
"write"
],
"rsid": "847016ce-bd6f-4ee0-873b-64ebbfc0888f"
}
],
"jti": "dad05c6e-e512-40b4-9b0b-73505e0b812e-1628838028099",
"aud": "https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc",
"sub": "5b9ee035-1e59-4c49-8ddd-d03c08f30949",
"azp": "uma-client"
}
This ticket is a request from a user to a resource. It does not mean that the user has access to this resource, it is a ticket used for getting a RPT.
Now, try to get a couple of RPT and PCT from this ticket, as John user :
./uma2-bash-client.sh --operation request_party_token_no_persistence\
--uma2-configuration-endpoint https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc/.well-known/uma2-configuration\
--access-token $ACCESS_TOKEN\
--ticket $TICKET
We can not get one :
{
"error": "invalid_ticket",
"error_description": "Invalid permission ticket."
}
with the right authorization given to John :
{
"upgraded": false,
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJYNFg5U1oyQVZIby11N25qNWNuRXNZWjFOa3R4M3RKVjl6RG5yejgyb004In0.eyJleHAiOjE2Mjg4Mzg5MTAsImlhdCI6MTYyODgzODYxMCwianRpIjoiMGU0MDA1YWYtNzc2OS00N2Y4LTk4NmYtZmIwY2E4NzFhZDZkIiwiaXNzIjoiaHR0cHM6Ly9hcHAucGxlYXNlLW9wZW4uaXQvYXV0aC9yZWFsbXMvNWFlNTVmMTItMTUxNS00N2M4LTk2NzgtYzc0MGIwYzg1MmZjIiwiYXVkIjoidW1hLWNsaWVudCIsInN1YiI6IjViOWVlMDM1LTFlNTktNGM0OS04ZGRkLWQwM2MwOGYzMDk0OSIsInR5cCI6IkJlYXJlciIsImF6cCI6InVtYS1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiNWUxNDk3MzgtN2Q5MC00ZmE4LTg4NDYtMTI4MDYxZWVjMmViIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vMTI3LjAuMC4xIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZGVmYXVsdC1yb2xlcy01YWU1NWYxMi0xNTE1LTQ3YzgtOTY3OC1jNzQwYjBjODUyZmMiXX0sInJlc291cmNlX2FjY2VzcyI6eyJ1bWEtY2xpZW50Ijp7InJvbGVzIjpbInVtYV9wcm90ZWN0aW9uIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJhdXRob3JpemF0aW9uIjp7InBlcm1pc3Npb25zIjpbeyJzY29wZXMiOlsid3JpdGUiXSwicnNpZCI6Ijg0NzAxNmNlLWJkNmYtNGVlMC04NzNiLTY0ZWJiZmMwODg4ZiIsInJzbmFtZSI6Im15cmVzb3VyY2UifV19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiam9obiJ9.HedtgEzaKl0dAm5uEE75JBcXrUsgWQY-DcL8QmK8p1zRtMOl4aRKY9cx1Jt534qRxe1prNIgB9SFw_bkBKleYLXKHWyoPsEDpnJw6hnzb-f2BHwgP8wOZrnwUjQuwQtf6404U9nNm_FbwNaJDgt9PY4GZ540kIvcM4npENRgYFDs6I7qxMkhnUiyevjZP4HknMxg9MwILcIWTmqfHEm2ZiHeuO2cqhP9XSC1yOZ80JAXWE7V_ia7Ea7TFrKU_wE7UVbaQpDSuTczdmvW1I7kFGED7T5sTR_5_xXjXyjkdQGsvYxfIbUUY8nkUnVVqNx5lNYQg3GUjybuNY07elIGTg",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmZjc0Y2MyNS0zYWQyLTRkZGYtYWFhNC0yZTkyZDg1NjI4YTkifQ.eyJleHAiOjE2Mjg4NDA0MTAsImlhdCI6MTYyODgzODYxMCwianRpIjoiNGMyMmZiNjYtZTk1Yi00ZmI3LTkyYjgtYTg4ZDhjYzc0MGY2IiwiaXNzIjoiaHR0cHM6Ly9hcHAucGxlYXNlLW9wZW4uaXQvYXV0aC9yZWFsbXMvNWFlNTVmMTItMTUxNS00N2M4LTk2NzgtYzc0MGIwYzg1MmZjIiwiYXVkIjoiaHR0cHM6Ly9hcHAucGxlYXNlLW9wZW4uaXQvYXV0aC9yZWFsbXMvNWFlNTVmMTItMTUxNS00N2M4LTk2NzgtYzc0MGIwYzg1MmZjIiwic3ViIjoiNWI5ZWUwMzUtMWU1OS00YzQ5LThkZGQtZDAzYzA4ZjMwOTQ5IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InVtYS1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiNWUxNDk3MzgtN2Q5MC00ZmE4LTg4NDYtMTI4MDYxZWVjMmViIiwiYXV0aG9yaXphdGlvbiI6eyJwZXJtaXNzaW9ucyI6W3sic2NvcGVzIjpbIndyaXRlIl0sInJzaWQiOiI4NDcwMTZjZS1iZDZmLTRlZTAtODczYi02NGViYmZjMDg4OGYiLCJyc25hbWUiOiJteXJlc291cmNlIn1dfSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIn0.qB1rKzsAz5XFm1Kj5oEhounCANfRnVSgnMCPTyxtTtc",
"token_type": "Bearer",
"not-before-policy": 0
}
The RPT looks like :
{
"exp": 1628838910,
"iat": 1628838610,
"jti": "0e4005af-7769-47f8-986f-fb0ca871ad6d",
"iss": "https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc",
"aud": "uma-client",
"sub": "5b9ee035-1e59-4c49-8ddd-d03c08f30949",
"typ": "Bearer",
"azp": "uma-client",
"session_state": "5e149738-7d90-4fa8-8846-128061eec2eb",
"acr": "1",
"allowed-origins": [
"http://127.0.0.1"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization",
"default-roles-5ae55f12-1515-47c8-9678-c740b0c852fc"
]
},
"resource_access": {
"uma-client": {
"roles": [
"uma_protection"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"authorization": {
"permissions": [
{
"scopes": [
"write"
],
"rsid": "847016ce-bd6f-4ee0-873b-64ebbfc0888f",
"rsname": "myresource"
}
]
},
"scope": "profile email",
"email_verified": true,
"preferred_username": "john"
}
Of course, Alice can also use this permission ticket :
{
"exp": 1628838962,
"iat": 1628838662,
"jti": "9896a9fb-c0c4-4a63-acdc-a92ccc5ae228",
"iss": "https://app.please-open.it/auth/realms/5ae55f12-1515-47c8-9678-c740b0c852fc",
"aud": "uma-client",
"sub": "2f6dddbf-83d3-400b-b9d1-6cc36eb4d11e",
"typ": "Bearer",
"azp": "uma-client",
"session_state": "8928df0c-398e-43b1-8c49-d872786d4a34",
"acr": "1",
"allowed-origins": [
"http://127.0.0.1"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization",
"default-roles-5ae55f12-1515-47c8-9678-c740b0c852fc"
]
},
"resource_access": {
"uma-client": {
"roles": [
"uma_protection"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"authorization": {
"permissions": [
{
"scopes": [
"write"
],
"rsid": "847016ce-bd6f-4ee0-873b-64ebbfc0888f",
"rsname": "myresource"
}
]
},
"scope": "profile email",
"email_verified": true,
"preferred_username": "alice"
}
Resources managed by the admin and policies
Enabling the authorizations in the client enables a new tab named « authorizations » in the client configuration.
This feature is used for global resources with policies that manage access.
With the same structure of a resource :
- name
- type
- url
- scopes
All resources (users and global) are listed :
Then, policies and authorizations could be defined. Those are « global rules », like « allow all members of a group to access XXXX resources types ».
Indeed, all the process of RPT token is all the same.
https://www.keycloak.org/docs/latest/authorizationservices/#resource_overview
https://www.keycloak.org/docs/latest/authorizationservices/#policy_overview
https://www.keycloak.org/docs/latest/authorizationservices/#permission_overview
Conclusion
This extension of Oauth2 is not only some new operations. It adds more features for resource management. It means that all resources are created in the authorization server, not only in the application.
This is why integrating UMA 2.0 in existing applications is really hard, with a total externalization of the authorizations process you probably defined in your application. Of course, for new applications with shared resources you MUST use UMA 2.0. Developing a custom shared resources management is a total nightmare.
The global conclusion we can provide about UMA 2.0 in your apps is :
Read and try UMA 2.0. If you do not want to deploy and use it, keep all the terminology in your infrastructure. A compliance with some standard features is easier to understand.
You may find the original article from our partner Please Open It here : https://blog.please-open.it/uma/
- How to enrich native metrics in KeyCloak - 21 août 2024
- Keycloak Authenticator explained - 7 mars 2024
- Keycloak OIDC authentication with N8N workflow - 1 décembre 2023