Keycloak authorization services REST API: paths and payloads

March 24, 2023 

Introduction

The Keycloak Authorization Services allows you to offload your application's authorization decisions to Keycloak instead of implementing them in your code. This way you can leverage Keycloak's advanced features like 2FA without any additional development on your part. If you're unfamiliar with the Authorization Services I suggest having a look at the Keycloak authorization services terminology first. You may also be interested in our sample code for managing Keycloak authorization services programmatically. To be perfectly honest, there is no separate Keycloak authorization services REST API. While the authorization services do support the UMA Protection API, that API is only needed if you want your resource servers (e.g. webapps) to be able manage their own authorization services configuration inside Keycloak. For example, you might want to provide your webapp users the capability to grant access (e.g. read, write) to their own resources to other users. In this article we will not cover the Protection API at all.

This article shows how to manipulate authorization data for a Keycloak client using the Keycloak Admin REST API. This use-case is particularly "lightly documented", meaning that there used to be zero documentation about this topic. Unless, of course, you count use of tcpdump and browser developer tools as documentation.

If you happen to use Ansible to manage your Keycloak configuration then please check these Ansible modules which we upstreamed to Ansible community.general collection:

Generic advise on the JSON payload format

In the JSON payloads mandary parameters are marked with bold. These are typically parameters that are reqiured to identify the object. Some other parameters may also be mandatory, for example those that define the type of the object. Values for mandatory parameters are surrounded by < and > signs to make them stand out, for example <policy-name>. The < and > signs should be removed from real JSON payloads.

Some parameters allow a fixed set of valid values. The valid values are separated with pipe signs ("|"). For example:

"param": "foo|bar|faz"

Where foo, bar and faz are all valid values.

Some parameters seem redundant, such as the logic parameter that in many cases has only one valid value ("positive"). Similarly the decisionStrategy parameter often has only one valid value ("UNANIMOUS"). If one of these parameters is missing the JSON payload might get rejected (or not). The same may happen if the parameter has an invalid value. It is also possible that values of these essentially useless parameters are just ignored.

Certain parameters in the JSON payload are optional. This means that they are not required to be present in the payload for it to be considered valid. The icon_uri field is a good example. You can add this to the JSON payload whenever the object in question support that field:

"icon_uri":"<some-uri>"

Another optional field is description which can be, but does not have to be, in the JSON payload:

"description":"<some-description>"

Managing Resources

Check if a Resource exists

Search for resources with matching name:

GET /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/resource/search?name=<resource-name>

Create a Resource

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/resource

JSON payload:

{
  "name":"<resource-name>",
  "displayName":"<resource-display-name>",
  "scopes":[],
  "attributes":{},
  "uris" :[],
  "ownerManagedAccess":""
}

Modify a resource

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/resource/<resource-id>

JSON payload:

{
  "_id":"<resource-id>",
  "name":"<resource-name>",
  "owner":
    {
      "id":"<client-id>",
      "name":"<client-name>"
    },
  "ownerManagedAccess":false,
  "displayName":"",
  "attributes":{},
  "uris":[],
  "scopes":[]
}

Linking resources with authorization scopes

To link a resource with one or more authorization scope add data to the scope parameter:

"scopes":[
  {
    "id":"<authorization-scope-id>",
    "name":"<authorization-scope-name>"
  }
]

Linking resources with types

A resource can belong to a type (see terminology). To make a resource of a certain type just define the type as a string:

"type":"<some-string>"

Managing attributes

You can add attributes to the JSON payload like this:

"attributes":
  {
    "param1":["foo"],
    "param2":["bar"]
  }

Delete a resource

HTTP method and path:

DELETE /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/resource/<resource-id>

Managing Authorization Scopes

Check if an Authorization Scope exists

Search for an authorization scope by name:

GET /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/scope/search?name=<scope-name>

Create an Authorization Scope

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/scope

JSON payload:

{
  "name":"<authorization-scope-name>",
  "displayName":""
}

Modify an Authorization Scope

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/scope/<scope-id>

JSON payload:

{
  "id":"<authorization-scope-id>",
  "name":"<authorization-scope-name>",
  "displayName":""
}

Delete an Authorization Scope

HTTP method and path:

DELETE /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/scope/<authorization-scope-id>

Managing Permissions

Check if a Permission exists

Search for Scope-based Permission by name:

GET /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/search?name=<permission-name>

NOTE: the policy part of the path shown above is not a typo.

Create a Scope Permission

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/permission/scope

JSON payload:

{
  "name":"<permission-name>",
  "scopes":["<scope-id>"],
  "decisionStrategy":"UNANIMOUS|AFFIRMATIVE|CONSENSUS",
  "type":"scope",
  "logic":"POSITIVE",
  "description":"",
  "resources":["<resource-id>"],
  "policies":["<policy-id>"]
}

Modify a Scope Permission

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/permission/scope/<permission-id>

JSON payload:

{
  "id":"<permission-id>",
  "name":"<permission-name>",
  "scopes":["<scope-id>"],
  "decisionStrategy":"UNANIMOUS|AFFIRMATIVE|CONSENSUS",
  "type":"scope",
  "logic":"POSITIVE",
  "description":"",
  "resources":["<resource-id>"],
  "policies":["<policy-id>"],
  "description":""
}

Create a Resource Permission

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/permission/resource

JSON payload:

{
  "type":"resource",
  "logic":"POSITIVE",
  "decisionStrategy":"UNANIMOUS|AFFIRMATIVE|CONSENSUS",
  "name":"<resource-based-permission-name>",
  "resources":["<resource-id>"],
  "policies":["<policy-id>"],
  "description":""
}

Modify a Resource Permission

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/permission/resource/<permission-id>

JSON payload:

{
  "id":"<resource-permission-id>",
  "name":"<resource-permission-name>",
  "type":"resource",
  "logic":"POSITIVE",
  "decisionStrategy":"UNANIMOUS|AFFIRMATIVE|CONSENSUS",
  "resources":["<resource-id>"],
  "policies":["<policy-id>"],
  "description":""
}

Delete a permission

HTTP method and path:

DELETE /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/<permission-id>

NOTE: the policy/<permission-id> part of the path shown above is not a typo.

Managing policies

Check if a policy exists

HTTP method and path:

GET /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/search?name=<policy-name>

Delete a policy

HTTP method and path:

DELETE /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/<policy-id>

Create a regex policy

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/regex

JSON payload:

{
  "type":"regex",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "name":"<policy-name>",
  "pattern":"<regular-expression>",
  "targetClaim":"<claim-name>",
  "description":""
}

Modify a regex policy

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/regex/<policy-id>

JSON payload:

{
  "id":"<policy-id>",
  "name":"<policy-name>",
  "type":"regex",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "targetClaim":"<claim-name>",
  "pattern":"<regular-expression>",
  "description":""
}

Create a role policy

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/role

JSON payload:

{
  "type":"role",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "name":"<policy-name>",
  "roles":[
    {"id":"<role-id>"}
  ],
  "description":""
}

The roles in the JSON payload can be either realm roles or client roles.

Modify a role policy

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/role/<policy-id> 

JSON payload:

{
  "id":"<policy-id>",
  "name":"<policy-name>",
  "type":"role",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "roles":[
    {"id":"<role-id>"}
  ],
  "description":""
}

Create a Client Policy

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/client

JSON payload:

{
  "type":"client",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "name":"<client-policy-name>",
  "clients":["<client-id>"],
  "description":""
}

Modify a Client Policy

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/client/<client-policy-id>

JSON payload:

{
  "id":"<client-policy-id>",
  "name":"<client-policy-name>",
  "type":"client",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "clients":["<client-id>"],
  "description":""
}

Create a Time Policy

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/time

Minimal JSON payload:

{
  "type":"time",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "name":"<time-policy-name>",
  "description":"",
   <time-settings>
}

Replace <time-settings> with a date range (e.g. month and monthEnd) or a time threshold (notAfter or notBefore):

"notAfter":"1970-01-01 00:00:00"
"notBefore":"1970-01-01 00:00:00"
"dayMonth":<day-of-month>
"dayMonthEnd":<day-of-month>
"month":<month>
"monthEnd":<month>
"year":<year>
"yearEnd":<year>
"hour":<hour>
"hourEnd":<hour>
"minute":<minute>
"minuteEnd":<minute>

Modify a Time Policy

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/time/<time-policy-id>

JSON payload:

{
  "id":"<time-policy-id>",
  "name":"<time-policy-name>",
  "type":"time",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "description":"",
  <time-settings>
}

Replace <time-settings> in the same way as when creating a Time Policy.

Creating a User Policy

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/user

JSON payload:

{
  "type":"user",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "name":"<user-policy-name>",
  "users":["<user-id>"],
  "description":""
}

Modify User Policy

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/user/<user-policy-id>

JSON payload:

{
  "id":"<user-policy-id>",
  "name":"<user-policy-name>",
  "type":"user",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "users":["<user-id>"],
  "description":""
}

Create a Client Scope Policy

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/client-scope

JSON payload:

{
  "type":"client-scope",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "name":"<client-scope-policy-name>",
  "clientScopes":[
    {"id":"<client-scope-id>"}
  ],
  "description": ""
}

Modify a Client Scope Policy

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/client-scope/<client-scope-policy-id>

JSON payload:

{
  "id":"<client-scope-policy-id>",
  "name":"<client-scope-policy-name>",
  "type":"client-scope",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "clientScopes":[
    {"id":"<client-scope-id>"}
  ],
  "description":""
}

Create an Aggregated Policy

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/aggregate

JSON payload:

{
  "type":"aggregate",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS|AFFIRMATIVE|CONSENSUS",
  "name":"<aggregated-policy-name>",
  "policies":["<policy-id>"],
  "description":""
}

Modify an Aggregated Policy

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/aggregate/<aggregate-policy-id>

JSON payload:

{
  "id":"<aggregated-policy-id>",
  "name":"<aggregated-policy-name>",
  "type":"aggregate",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS|AFFIRMATIVE|CONSENSUS",
  "policies":["<policy-id>"]
  "description":"",
}

Create a Group Policy

HTTP method and path:

POST /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/group

JSON payload:

{
  "type":"group",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "name":"<group-policy-name>",
  "groups":[
    {"id":"<group-id>","path":"<group-path>"}
  ]
  "groupsClaim":"<groups-claim>",
  "description":""
}

Modify a Group Policy

HTTP method and path:

PUT /auth/admin/realms/<realm>/clients/<client-id>/authz/resource-server/policy/group/<group-policy-id>

JSON payload:

{
  "id":"<group-policy-id>",
  "name":"<group-policy-name>",
  "type":"group",
  "logic":"POSITIVE|NEGATIVE",
  "decisionStrategy":"UNANIMOUS",
  "groups":[
    {"id":"<group-id>",
     "extendChildren":false,
     "path":"<group-path>"
    }
  ],
  "groupsClaim":"<groups-claim>",
  "description":""
}

What about Javascript policies?

Managing Javascript policies using the Admin UI has been deprecated since Keycloak 7.0.1. It is possible to re-enable the feature but I believe it went away for good in first Quarkus versions of Keycloak. So, there is little point in documenting how Javascript policies could be added in older Keycloak versions. It is better to deploy those policies as JAR files anyways.

Samuli Seppänen
Samuli Seppänen
Author archive
menucross-circle