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.

Keycloak and UMA 2.0 with bash

First of all, create a confidential client with authorizations enabled.

Keycloak and UMA 2.0 with bash

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.

Keycloak and UMA 2.0 with bash

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.

Keycloak and UMA 2.0 with bash

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 »)

Keycloak and UMA 2.0 with bash

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

Keycloak and UMA 2.0 with bash

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 :

Keycloak and UMA 2.0 with bash

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/

Mathieu PASSENAUD
Les derniers articles par Mathieu PASSENAUD (tout voir)