Fine-Grained Keycloak Authorization with ABAC and ReBAC
- Share:
Authorization is a critical part of application security, ensuring users have access only to the resources they’re entitled to. Keycloak is an open-source Identity and Access Management (IAM) tool that provides built-in features for user authentication and authorization. However, while Keycloak offers some capabilities for role-based access control (RBAC), its limitations become apparent when implementing advanced, fine-grained authorization models like Attribute-Based Access Control (ABAC) or Relationship-Based Access Control (ReBAC).
In this tutorial, we’ll explore how to enhance Keycloak Authorization using Permit.io, a platform designed to simplify access control with no-code and API-driven tools. Permit extends Keycloak's functionality by introducing dynamic policies, fine-grained permissions, and support for ABAC and ReBAC.
By the end of this guide, you’ll learn how to:
- Configure Keycloak to manage user authentication and basic authorization.
- Integrate Permit.io for implementing ABAC and ReBAC.
- Build advanced authorization workflows for real-world scenarios, such as:
- A banking system enforcing conditional transfers.
- A Google Drive-like model for resource-based inheritance of permissions.
The tutorial will follow a specific real-life use-case example - a banking system application that allows performing actions based on specific conditions, like:
A user can only initiate a transfer of over ÂŁ1,000 if their account has been active for more than one year
As well as a relationship-based access control use case -
If a user has access to a folder, they automatically have access to the files within that folder.
Fine-Grained Authorization with ABAC and ReBAC
Modern applications require more than just basic access control mechanisms. Fine-Grained Authorization (FGA) ensures users can access specific resources or perform actions based on detailed, context-aware rules. This level of granularity is critical for meeting security, compliance, and user experience requirements in most of today's systems.
Two of the most effective models for implementing FGA are Attribute-Based Access Control (ABAC) and Relationship-Based Access Control (ReBAC). Both models allow developers to go beyond simple role-based access control (RBAC) by introducing dynamic, flexible policies.
Let’s go over exactly how they achieve this:
What is ABAC?
Attribute-Based Access Control (ABAC) is an advanced authorization model that evaluates attributes to make access decisions. Attributes can describe:
- Users: e.g., age, role, department, or account status.
- Resources: e.g., type, owner, or sensitivity level.
- Environment: e.g., time of day, IP address, or geographic location.
ABAC policies combine these attributes into conditional rules, enabling dynamic and fine-grained control. For example:
- “Allow a user to transfer funds over £1,000 only if their account has been active for at least one year.”
ABAC’s flexibility makes it ideal for scenarios where static roles are insufficient. Learn more about ABAC in our comprehensive guide.
What is ReBAC?
Relationship-Based Access Control (ReBAC) focuses on the connections between users and resources. Rather than relying solely on attributes, ReBAC examines the relationships users have with resources or other users. For example:
- In a project management tool, a user with the "Manager" relationship to a project should automatically have access to all tasks and files within that project.
ReBAC simplifies complex access hierarchies by inheriting permissions through relationships. This makes it an excellent choice for collaborative platforms, hierarchical data structures, and systems requiring contextual access control.
Dive deeper into ReBAC and its applications here.
The Limitations of Keycloak Authorization
While Keycloak authorization provides some basic built-in capabilities, its limitations become evident when dealing with advanced, fine-grained access control. These are the core challenges:
Feature | Keycloak Only | Keycloak + Permit.io |
---|---|---|
Basic RBAC | âś… | âś… |
ABAC Support | Limited | âś… |
ReBAC Support | ❌ | ✅ |
Decentralized Policies | ❌ | ✅ |
Keycloak ABAC is Hard to Configure
Keycloak supports ABAC through JavaScript-based policies, but this approach is cumbersome and non-scalable. Writing and maintaining JavaScript rules for every attribute condition can quickly become overwhelming, especially in large applications with dynamic requirements. This lack of a user-friendly interface or API for defining ABAC policies hinders productivity and increases the risk of errors.
ReBAC is Unsupported
Keycloak lacks the data model necessary to implement Relationship-Based Access Control. Relationships between users and resources (e.g., “Manager of Project X”) are not natively supported, making it impossible to enforce ReBAC policies. Developers must resort to custom solutions, which are complex, time-consuming, and prone to inconsistencies.
Monolithic Architecture
Keycloak’s monolithic architecture centralizes decision-making and enforcement, which is a significant limitation for modern distributed systems. Decentralized policy enforcement—where decisions are made closer to the resources or users—is critical for scalability, low latency, and fault tolerance. Unfortunately, Keycloak is not designed to handle such decentralized scenarios, forcing organizations to rely on external tools for distributed enforcement.
These limitations highlight the need for complementary solutions to extend Keycloak’s capabilities. Permit.io addresses these gaps by providing scalable, dynamic, and fine-grained authorization with seamless integration into Keycloak.
Adding ABAC to Keycloak
Attribute-Based Access Control (ABAC) allows you to define fine-grained rules based on user, resource, and environmental attributes. In this section, we’ll demonstrate how to enhance Keycloak with ABAC by integrating it with Permit.io.
The example will implement a banking system rule: “A user can only initiate a transfer of over £1,000 if their account has been active for more than one year.”
Step 1: Define a Resource
In Permit.io, resources are entities that users interact with. For our banking example, create a resource called Transfer
with a single action, send
.
- Navigate to the Resources section in Permit.io.
- Create a new resource:
- Name: Transfer
- Action: send
- Add an attribute to the resource:
- Attribute Name:
amount
- Data Type: Number
You’ll now see the Transfer resource listed with its amount
attribute.
We can see the Transfer resource along with its amount attribute.
Step 2: Define User Attributes
Attributes associated with users define their eligibility for certain actions. For this example, add an attribute to track how long a user’s account has been active.
- Go to the Directory section in Permit.io and select User Settings.
- Create a new attribute:
- Attribute Name:
accountTime
- Data Type: Number
Set the accountTime
attribute for relevant users. For instance, assign a value of 4
to indicate a user whose account has been active for four years.
Step 3: Create a Role
Roles provide a predefined set of permissions. For this example, create a role for account holders.
- Navigate to Roles in Permit.io.
- Create a new role:
- Name: AccountHolder
- Assign this role to relevant users.
We have created a new role called Account Holder. Since we are working with advanced concepts to establish more granular rules, we have to define both User Sets and Resource Sets.
Step 4: Create User Sets
A User Set groups users based on attribute conditions, simplifying policy management.
- Navigate to the Policy Editor in Permit.io.
- Create a newUser Set:
- Name: Premium Account Holders
- Condition:
user.accountTime >= 1
user.roles contains AccountHolder
This groups users who have held accounts for at least one year and are assigned the AccountHolder
role.
Here, we have defined it as Premium Account Holder and added a Condition Group:
• user.AccountTime should be greater than 1 year, and
• user.roles should include AccountHolder.
On the Policy Editor page, the newly added User Set is available.
Step 5: Create Resource Sets
A Resource Set groups resources based on their attributes.
- In thePolicy Editor, create twoResource Sets:
- Name: Transfers Less Than ÂŁ1,000
- Condition:
resource.amount <= 1000
- Name: Transfers Greater Than ÂŁ1,000
- Condition:
resource.amount > 1000
These sets segment resources into categories based on the amount
attribute.
Here we have set a new resource set called “Transfer less than 1000”
We have created another ABAC Resource Set called “Transfer More Than 1000”.
Step 6: Create ABAC Policies
ABAC policies tie User Sets, Resource Sets, and actions together. Now, on the Policy Editor page, you can see that all resource sets are available for setting up granular-level permissions.
- Go to the Policy Editor in Permit.io.
- Create a policy:
- Name: Premium Transfer Policy
- User Set: Premium Account Holders
- Resource Set: Transfers Greater Than ÂŁ1,000
- Action: send
This policy ensures only users in the Premium Account Holders set can perform the send
action on resources in the Transfers Greater Than ÂŁ1,000 set.
Here, we can now assign permissions to AccountHolder and Premium Account Holder.
Finally, we can assign the Account Holder role to our user, as shown below.
That’s it! We have set up our ABAC policy, and it will work as expected, as we discussed earlier. However, if you want to check the ABAC at the application level, it’s just as simple.
Step 7: Enforce ABAC in Your Application
Use Permit.io’s SDK to enforce ABAC policies dynamically. Here’s how to integrate the check into your application:
const isAuthorized = await permit.check(
{
key: "bill-897-006",
attributes: {
"account-time": 4 // User has been with the account for 4 time units
}
},
"send",
{
type: "transfer",
attributes: {
amount: 350
}
}
);
if (isAuthorized) {
await processTransfer();
} else {
throw new ForbiddenException('Transfer not authorized');
}
It’s the same function we have been using in our application, and it can also help us check ABAC-level permissions.
Amazing! You have completed a full ABAC system in Permit.
Now, let’s move on to ReBAC. Let’s try to understand ReBAC a bit.
Adding ReBAC to Keycloak
Relationship-Based Access Control (ReBAC) focuses on user-resource relationships to define access permissions. In this section, we’ll implement a Google Drive-like system: If a user has access to a folder, they automatically have access to the files within that folder.
Creating resources:
Resources are the fundamental entities that users interact with. The first stage in creating a permissions framework is recognizing and classifying these resources, which may be a project, a module, or a subdirectory. For this, we need two resources: Folder and File.
Step 1: Define Resources
Create two resources in Permit.io: Folder and File.
- Navigate to Resources in Permit.io.
- Add two resources:
- Folder: Actions:
read
,write
,delete
. - File: Actions:
read
,edit
,delete
.
We have two resources, Folder and File, with their respective actions (read, create, update, delete, etc). Now, we need to define the relationship between them.
Step 2: Define Relationships
Relations determine how different resources are connected or related to each other. By defining relations, we can create a structured hierarchy or network, enabling more granular control over access permissions based on these connections.
- Go to the Relationships tab in Permit.io.
- Create a relationship:
- Name: Parent-Child
- Definition: A
Folder
is the parent of aFile
.
This relationship allows permissions on a folder to cascade to its files.
Here, we have added the relationship where the Folder is the parent of the File. Now, we need to associate the roles with the resources.
Step 3: Add Roles to Resources
Roles define what actions users or groups can perform on specific resources. Associating roles with resources is essential for granular ReBAC.
- Navigate to the Roles section in Permit.io.
- Add roles for each resource:
- Folder Admin:
- Actions:
read
,write
,delete
for folders.
- File Editor:
- Actions:
read
,edit
for files.
By associating these roles with resources, you define the levels of access users have relative to specific entities.
I have defined the roles for the two resources (Admin and Editor). We have defined our resources, relationships, and roles. Now, we need to create role derivation.
Step 4: Set Up Role Derivations
Role derivation allows for the dynamic assignment or inheritance of roles based on certain conditions or relationships. By enabling roles to be contextually derived depending on variables such as a user's relationship to a resource, this step guarantees flexibility in the ReBAC system.
- Go to the Policy Editor in Permit.io.
- Create a role derivation:
- Source Role: Folder Admin
- Derived Role: File Editor
- Relationship: Parent-Child
This ensures that a user with Folder Admin
permissions automatically inherits File Editor
permissions for all files within that folder.
We created a role derivation for the Editor role on the Folder resource. Now, a user who has folder editor access will automatically gain access to all files inside the folder.
In summary, assigning permissions based on users’ roles, Relation Base Access Control (ReBAC) looks at the relationship and interactions between users, their roles, and the resources they want to access.
Step 5: Enforce ReBAC in Your Application
Use Permit.io’s SDK to dynamically enforce ReBAC permissions. Here’s how you can check permissions for a file:
This is a standard file controller in NestJS. If we have a route to edit a file, it will first check the permission using file:${fileId}
, for example, file:2025_report_list
. The permission will then perform the necessary checks and return either true
 or false
 based on the user's permissions.
// files.controller.ts
import { Controller, Param, Put, UseGuards, ForbiddenException } from '@nestjs/common';
import { PermitService } from './permit.service';
@Controller('files')
export class FilesController {
constructor(private readonly permitService: PermitService) {}
@Put(':fileId')
async editFile(@Param('fileId') fileId: string, @Param('userId') userId: string) {
const action = 'edit'; // Define the action
const resource = `file:${fileId}`; // Define the resource
const hasPermission = await this.permitService.checkPermission(userId, action, resource);
if (!hasPermission) {
throw new ForbiddenException('You do not have permission to edit this file');
}
// Proceed with the file editing logic
return { message: `File ${fileId} edited successfully!` };
}
}
Conclusion
Keycloak provides powerful tools for managing authentication and basic access control, but its limitations in handling fine-grained authorization often necessitate external solutions. By integrating Permit.io with Keycloak, you can elevate your authorization system to handle complex scenarios using ABAC and ReBAC. This combination allows you to implement dynamic, scalable, and context-aware policies that adapt to real-world requirements.
In this guide, we covered:
- Adding ABAC to Keycloak to enforce attribute-driven rules, such as conditional transfers in a banking system.
- Implementing ReBAC to enable relationship-based permissions, such as cascading folder-to-file access in a Google Drive-like system.
- Leveraging Permit.io’s no-code tools and APIs to simplify policy management and enforcement.
With these techniques, you can unlock the full potential of Keycloak authorization while ensuring flexibility, scalability, and ease of management.
Next Steps
Here are some actionable steps to further enhance your Keycloak authorization system:
- Sign up for Permit.io to start building advanced authorization workflows tailored to your application's needs.
- Dive into the documentation for detailed guides and API references.
- Understand the differences between RBAC, ABAC, and ReBAC in our in-depth guide.
- Got questions or ideas? Join the Permit.io Slack community to connect with other developers building authorization systems.
Written by
Gabriel L. Manor
Full-Stack Software Technical Leader | Security, JavaScript, DevRel, OPA | Writer and Public Speaker