- Introduction
- Generic advise on the JSON payload format
- Managing Resources
- Managing Authorization Scopes
- Managing Permissions
- Managing policies
- Check if a policy exists
- Delete a policy
- Create a regex policy
- Modify a regex policy
- Create a role policy
- Modify a role policy
- Create a Client Policy
- Modify a Client Policy
- Create a Time Policy
- Modify a Time Policy
- Creating a User Policy
- Modify User Policy
- Create a Client Scope Policy
- Modify a Client Scope Policy
- Create an Aggregated Policy
- Modify an Aggregated Policy
- Create a Group Policy
- Modify a Group Policy
- What about Javascript policies?
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:
- keycloak_authz_authorization_scope module
- keycloak_authz_custom_policy module
- keycloak_authz_permission module
- keycloak_authz_permission_info module
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.