Keycloak’s resource-based authorization isn’t about granting permissions to users; it’s about granting permissions to the things users want to access.
Let’s watch it in action. Imagine a simple API for managing "documents." A user might want to list all documents, read a specific document, or update a document.
First, we define the "resource" in Keycloak. This is the "document" itself. We can create a resource type in Keycloak’s admin console under Clients -> [Your Client] -> Authorization -> Resource Types. Let’s call it Document.
{
"name": "Document",
"displayName": "Document Resource"
}
Now, we can create individual resources of this type. A specific document, say "Project Proposal Q3," would be a resource.
{
"name": "project-proposal-q3",
"displayName": "Project Proposal Q3",
"resourceType": "Document",
"uris": ["/documents/project-proposal-q3"]
}
We can also define "scopes" for these resources. Scopes are like the actions that can be performed on a resource. For our Document resource, we might have view and edit scopes.
{
"name": "view",
"displayName": "View Document",
"iconUri": "..."
}
{
"name": "edit",
"displayName": "Edit Document",
"iconUri": "..."
}
With resources and scopes defined, we can now create "policies" that determine who can access what. A common policy type is the "Resource Scope" policy. This policy links specific scopes to specific resources.
Let’s say we want to grant the view scope for the project-proposal-q3 resource. We’d create a policy:
{
"name": "View Proposal Q3 Policy",
"description": "Grants view access to Project Proposal Q3",
"type": "resource",
"logic": "POSITIVE",
"resources": ["project-proposal-q3"],
"scopes": ["view"],
"ownerManagedAccess": false
}
Then, we create an "Access Token" policy. This policy determines who or what gets the permission defined by the resource scope policy. For example, we could have a policy that grants access to users belonging to the "Developers" group.
{
"name": "Developer Group Access",
"description": "Access for users in the Developers group",
"type": "group",
"groups": ["developers"],
"ownerManagedAccess": false
}
Finally, we combine these policies into an "Authorization" rule. This rule specifies that if the Developer Group Access policy is met, then the View Proposal Q3 Policy is granted.
{
"name": "View Q3 Proposal Rule",
"description": "Allow Developers to view Q3 Proposal",
"logic": "POSITIVE",
"policies": ["Developer Group Access", "View Proposal Q3 Policy"],
"ownerManagedAccess": false
}
When a user’s application requests access to /documents/project-proposal-q3 with the view scope, Keycloak checks the policies. If the user is in the "Developers" group, the Developer Group Access policy evaluates to true. Then, Keycloak checks if the View Proposal Q3 Policy is granted for the project-proposal-q3 resource and view scope. If all conditions align, Keycloak issues an access token that includes the necessary permissions. The application then uses this token to authorize the request.
The surprising part is how this shifts the authorization paradigm. Instead of thinking "can User X do Action Y on Resource Z?", you think "is this specific instance of Resource Z allowed to be accessed with Action Y by whoever is making the request?" This allows for incredibly granular control, even down to individual instances of data.
The real power comes when you start using "JavaScript" or "Drools" policies. These allow you to write custom logic. For instance, you could create a policy that states a user can only edit a document if they are the "owner" of that document, and the document’s status is "draft." The "owner" could be stored as a custom attribute on the resource itself.
{
"name": "Document Owner Edit Policy",
"description": "Allow document owner to edit if status is draft",
"type": "javascript",
"logic": "POSITIVE",
"config": {
"code": "var resource = evaluation.getResource();\nvar user = evaluation.getUser();\nvar owner = resource.getAttributes().get('owner');\nvar status = resource.getAttributes().get('status');\n\nif (owner && owner.contains(user.getUsername()) && status && status.contains('draft')) {\n actions.grant();\n} else {\n actions.deny();\n}"
},
"ownerManagedAccess": false
}
This policy would then be linked to the edit scope for the Document resource type, and an authorization rule would ensure it’s evaluated. The application would need to pass the document’s owner and status as resource attributes during the authorization request.
The next step to explore is how Keycloak evaluates these policies and permissions at runtime, and how you can optimize that process.