Keycloak Authorization Services - RPT, permissions or a decision only

Keycloak Authorization Services - RPT, permissions or a decision only

This is a clarification to the previous write up about Keycloak Authorization Services[1]. The documentation of the response_mode documents the two values which can be used: decision and permissions. In the first Keycloak article[2], I have wrongly assumed that no response_mode in the grant_type=urn:ietf:params:oauth:grant-type:uma-ticket call implies the value of permissions.

Mmm, that was a wrong assumption.

Now, looking at the documentation and trying it out, the distinction seems pretty obvious. It turns out there are three types of responses for this grant_type.

  1. Asking for RPT (requesting party token - an access token with permissions): without response_mode parameter
1
2
3
4
5
6
curl --silent -X POST \
  ${KEYCLOAK_TOKEN_URL} \
  -H "Authorization: Bearer ${access_token}" \
  --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  --data "audience=customers" \
  --data "permission=CustomerB#customer-b" | jq '.'

where the ${access_token} is the outcome of:

1
2
3
4
export access_token=`curl --silent -u customers:${KEYCLOAK_CLIENT_SECRET} \
    -k -d "grant_type=password&username=member@service-team&password=${USER_PASSWORD}&scope=email profile" \
    -H "Content-Type:application/x-www-form-urlencoded" \
    ${KEYCLOAK_TOKEN_URL} | jq '.access_token' -r`

the response looks like this, the access_token is the RPT:

1
2
3
4
5
6
7
8
9
{
  "upgraded": false,
  "access_token": "eyJhbGciOiJSUzI1NiIsI...n8AC51T1AMwDtoqfCEXrdwcrQ",
  "expires_in": 300,
  "refresh_expires_in": 1800,
  "refresh_token": "eyJhbGciOiJIUz...RG3zFus",
  "token_type": "Bearer",
  "not-before-policy": 0
}
  1. Asking for permissions - decision only: response_mode=decision

By adding:

--data "response_mode=decision"

the full call is:

1
2
3
4
5
6
7
curl --silent -X POST \
  ${KEYCLOAK_TOKEN_URL} \
  -H "Authorization: Bearer ${access_token}" \
  --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  --data "audience=customers" \
  --data "response_mode=decision" \
  --data "permission=CustomerB#customer-b" | jq '.'

and returns only a decision. If user has access to the resources, the response is:

1
2
3
{
  "result": true
}

otherwise, the response is:

1
2
3
4
{
  "error": "access_denied",
  "error_description": "not_authorized"
}
  1. Asking for permissions: response_mode=permissions

By specifying

--data "response_mode=permissions"

full call being:

1
2
3
4
5
6
7
curl --silent -X POST \
  ${KEYCLOAK_TOKEN_URL} \
  -H "Authorization: Bearer ${access_token}" \
  --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  --data "audience=customers" \
  --data "response_mode=permissions" \
  --data "permission=CustomerB#customer-b" | jq '.'

Keycloak answers:

1
2
3
4
5
6
7
8
9
[
  {
    "scopes": [
      "customer-b"
    ],
    "rsid": "00f34b81-c45b-4e28-b267-45fad4e48b4d",
    "rsname": "CustomerB"
  }
]

However, what is more interesting, is the call without specific permissions listed:

1
2
3
4
5
6
curl --silent -X POST \
  ${KEYCLOAK_TOKEN_URL} \
  -H "Authorization: Bearer ${access_token}" \
  --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  --data "audience=customers" \
  --data "response_mode=permissions" | jq '.'

which returns all available permissions for the original access token:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[
  {
    "scopes": [
      "customer-a"
    ],
    "rsid": "715f6cc5-8ca7-44e4-a8ce-924493db76b1",
    "rsname": "CustomerA"
  },
  {
    "scopes": [
      "customer-b"
    ],
    "rsid": "00f34b81-c45b-4e28-b267-45fad4e48b4d",
    "rsname": "CustomerB"
  }
]

§Listing permissions without the name

Optionally, we can ask Keycloak to not return resource names, only IDs. This is achieved by using response_include_resource_name=false, an example:

1
2
3
4
5
6
7
curl --silent -X POST \
  ${KEYCLOAK_TOKEN_URL} \
  -H "Authorization: Bearer ${access_token}" \
  --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  --data "audience=customers" \
  --data "response_mode=permissions" \
  --data "response_include_resource_name=false" | jq '.'

gives:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
  {
    "scopes": [
      "customer-a"
    ],
    "rsid": "715f6cc5-8ca7-44e4-a8ce-924493db76b1"
  },
  {
    "scopes": [
      "customer-b"
    ],
    "rsid": "00f34b81-c45b-4e28-b267-45fad4e48b4d"
  }
]