Keycloak integration with OpenFGA (based on Zanzibar) for Fine-Grained Authorization at Scale (ReBAC)
In Short
With the growing importance of zero-trust architecture which is based on the principle that nothing can be trusted, questions around the level of access granted have become critical. Furthermore, security and compliance are mandatory problems to tackle in any architecture, and authorization is a crucial part of any solution having in mind that the 2021 top OWASP risk was a broken access control. And last but not least, how to make authorization flexible, fast and scalable.
Taking into consideration the aim of the article is to provide an open-source authorization framework to solve those problems thanks to the implementation of Keycloak open-source Identity and Access Management platform integrated with OpenFGA, which is an open source solution for Fine-Grained Authorization that applies the concept of ReBAC (created by the Auth0 inspired by Zanzibar).
Therefore, I have implemented Keycloak for handling the authentication with the standard OpenID Connect and also taking advantage of the Keycloak role model for managing the user access. I am still using the standard OAuth 2.0 for API protection, since the application is sending the access token when it is calling the service but I have just used it for getting identity claims. This architecture is focused on decoupling the authorization part.
Additionally, OpenFGA is responsible for applying fine-grained access control in a high performance and flexible authorization / permission system. The OpenFGA service answers authorization checks by determining whether a relationship exists between an object and a user. It uses the authorization model of the system and the relationship tuples present in the system at that time in order to make a decision.
What’s new: October 8, 2023 😀
A new version of the PoC is now available. We have achieved direct integration between the Access Manager platform and the OpenFGA solution, thanks to the use of the OpenFGA Java SDK to publish events over HTTP. In the past, we used a Kafka cluster to synchronize events between platforms when the SDK wasn’t available. Therefore, I’ve updated some sections to showcase the newly improved and simplified ❤️ authorization architecture.
What’s new: June 29, 2024
I wrote a new article explaining the approach for low-code authorization with OpenFGA and the decoupling pattern by applying access control at the API Gateway. If you are interested, check out the Mastering Access Control: Low-Code Authorization with ReBAC, Decoupling Patterns and Policy as Code article.
So, one thing is still missing: how do we connect the Keycloak permission model events with the OpenFGA authorization model (ReBAC) ?
Well, I developed a new custom extension known as Service Provider Interfaces (SPI) called keycloak-openfga-event-publisher which basically:
- Listens to the Keycloak events, for instance, an User Role Assignment.
- Translates this event to an OpenFGA tuple key using the OpenFGA SDK (more details are available further).
- Publishes the event directly to the OpenFGA Solution
Here is a basic example of how all the components work together, for instance, for allowing an Analyst to have rights to see the product catalog:
a) We have modeled the Analyst role related to the view product role and the former has been assigned to the user Peter in Keycloak
b) The custom Keycloak extension “listens” to two events in this case, parses them and creates two OpenFGA tuples, which represents the relationship between those roles and that the user Peter is related to the Analyst role. As a final step, it publishes the event to the OpenFGA solution
c) Then the application or API can do an authorization check invoking the OpenFGA service asking for example:
“Is Peter related to the role view product as assignee?” Allowed
In this case OpenFGA allows the operation because the user has an implied relationship to the child role thanks to the parent role.
Another cool feature of this extension is its capability to discover the OpenFGA authorization model and determine which events are handled. This gives you the flexibility to choose your authorization model, whether it’s RBAC, GBAC, or both 🙌.
Here I described the most simple scenario; other details are described in the article to give you a complete idea.
Some Background
Authentication vs Authorization
Authentication (or AuthN) is a process that ensures a user’s identity. Authorization (or AuthZ) means determining if a user can perform a certain action on a particular resource.
OpenID Connect (OIDC)
Identity layer on top of the OAuth 2.0 protocol. OIDC provides authentication by the ID token which contains information about the identity.
OAuth 2.0
It is one of the most well-known security policies. It provides authorization via an access token (JWT) containing:
- Scopes: are used to express what an application can do on behalf of the user.
- Identity claims: are used to provide identity user attributes information.
JSON web token (JWT)
Open standard used to share information between two parties, it is signed using a cryptographic algorithm to ensure that the claims cannot be altered after the token is issued and it could also be encrypted.
Access Control
Access control involves users, resources and applications, and it is a complex model. Role-based Access Control — or RBAC: is an authorization model used to determine access control based on predefined roles. In this approach you proceed to assign roles to users, and permissions to roles, and use those to decide who can do what. E.g. Simon is an Admin who can administer the Store application. Keycloak permissions model is based on RBAC.
On the other hand, the OpenFGA authorization model is based on Relationship-based authorization, or ReBAC, which means organizing permissions based on relationships between resources.
Nevertheless, there is no fixed path to follow, depending on the business requirements we can apply one or a mix of the access control models.
Relationship-based authorization (ReBAC)
ReBAC allows expressing rules based on relations that users have with objects and that objects have with other objects. For example, a user can view a document if they can view its parent folder.
In many cases, roles would fit in well as relations on an object type. In this case I implemented custom roles for accomplishing the integration with the Keycloak permission model.
Zanzibar
Zanzibar is “Google’s Consistent, Global Authorization System” for “determining whether online users are authorized to access digital objects” across their products (Google Drive, YouTube, Google Photos, etc..). It uses (object, relation, user) tuples to store relation data and traverses those relations to check if there is a relation between a user and an object.
Zanzibar scales to trillions of objects, billions of users and millions of authorization requests per second.
Proposed Authorization Framework
The components involved in the authorization framework are described below:
The diagram described before has the following components:
- Keycloak: Responsible for handling the authentication
- OpenFGA: Responsible for applying fine-grained access control. The OpenFGA service answers authorization checks by determining whether a relationship exists between an object and a user
- New Custom extension Keycloak OpenFGA Event Publisher: Responsible for parsing and sending the events to the OpenFGA Solution
Each component is described below in more detail.
OpenFGA - Authorization Layer
OpenFGA is an open source solution to Fine-Grained Authorization that applies the concept of ReBAC. It was created by the Auth0 FGA team and was inspired by Zanzibar. It was designed for reliability and low latency at a high scale. It offers an HTTP API and has SDKs for programming languages including Node.js/JavaScript, GoLang and .NET.
The OpenFGA service answers authorization checks by determining whether a relationship exists between an object and a user.
OpenFGA Concepts
OpenFGA is based on the ReBAC model, this is why it’s built to quickly and reliably make authorization checks. This means providing an answer to a question: “Can user U perform action A on object O?”
ReBAC systems determine access from a user’s relation to an object. Authorization decisions are then yes or no answers to the question: “Does user U have relation R with object O?”.
Please visit the official link to review the main concepts of OpenFGA, the most important ones are below:
- An authorization model is a combination of one or more type definitions. This is used to define the permission model of a system
- An user is an entity in the system that can be related to an object
- An object represents an entity in the system.
- A relation is a string defined in the type definition of an authorization model that defines the possibility of a relationship between an object of the same type as the type definition and a user in the system.
- A relationship tuple is a tuple consisting of a user, relation and object stored in OpenFGA.
- A check request is a call to the OpenFGA check endpoint that returns whether the user has a certain relationship with an object.
There are a lot of super cool examples about the data modeling and theory behind the scenes, this is why I recommend reviewing the oficial doc.
In our case, I have implemented the authorization model of custom roles based on the Keycloak Role and Group model. This authorization model is based on the following premises:
- Keycloak administrators are able to create arbitrary sets of roles with different permissions that govern the users’ access to objects.
- It is not known beforehand (at the time of Authorization Model creation) what the application roles are.
OpenFGA Keycloak Authorization Model
Based on the Keycloak’s model (see section Keycloak — Role model) we can define the following cases (I will start with the simpler to the more complex case)
- A certain user can be assigned to a certain role (user role assignment)
- A certain role can be related to a parent role (role to role assignment)
- A certain user can be assigned to a certain group (user group
- membership)
- A certain group can be related to a certain role (group role assignment)
Take into consideration these premises, we can represent all of these cases with the following Keycloak authorization model (OpenFGA playground):
It is amazing that with this simple authorization model we can represent all the use cases.
In this new version of the authorization model, I’ve used the feature of direct type restriction to control the type of object allowed in the relation.
Furthermore, OpenFGA supports implied relationships. This means that an implied (or computed) relationship R exists between user X and object Y if user X is related to an object Z that is in a direct or implied relationship with object Y, and the OpenFGA authorization model allows it. In other words, in our case OpenFGA knows the implicit roles the user has inherited by the direct assignment of a parent role or group.
OpenFGA SDK
It offers an HTTP API and has SDKs for programming languages including Node.js/ JavaScript, GoLang and Java (new). Here I will show an example how to perform a check request to determine whether a user has a certain relationship with an object.
// Run a check
const { allowed } = await fgaClient.check({
tuple_key: {
user: 'user:paula',
relation: 'assignee',
object: 'role:view-product',
},
});ty
// allowed = true
In my scenario, I have used the SDK in two different cases:
- API: When performing the access control check in the API.
- Keycloak extension: When parsing and synchronizing the events from Keycloak to OpenFGA
Keycloak - Identity Layer
Open Source Identity and Access Management system, built and maintained by Red Hat. It provides single sign-on with strong session management capabilities. It means allowing users to access multiple applications, while only having to authenticate once. Keycloak builds on industry standard protocols supporting OAuth 2.0, OpenID Connect and SAML 2.0.
Authentication
In our scenario each application is integrated with Keycloak through OpenID Connect. This means that the application is delegating the authentication process to the SSO platform.
On the other hand, OAuth 2.0 is a delegated authorization framework for REST / APIs. It enables apps to obtain limited access (scopes) to user’s data (without giving away a user’s password).
After the user was authenticated the application receives two tokens, the id token as identity token includes information about the user and it is meant to be used by the application only. The access tokens are used to inform an API that the bearer of the token has been authorized to access the API and perform a predetermined set of actions.
Keycloak Roles Model
Keycloak supports role modeling and it also has different kinds of roles: Realm roles, Client roles and composite Roles:
- Realm Role: It is a global role, belonging to that specific realm. You can access it from any client and map to any user. Ex Role: ‘Global Admin, Admin’
- Client Role: It is a role which belongs only to that specific client. You cannot access that role from a different client. Ex Roles: Product Analyst, Customer’
- Composite Role: It is a role that has one or more roles (realm or client ones) associated with it.
It also supports modeling Groups, which can hold users as members and roles.
By implementing this model you can define the user’s access based on roles, groups or both. These operations produce events in Keycloak, for instance, when you assign a user to a role that you can “listen” to the event and decide which the next steps are. This functionality is used by the following custom extension.
Keycloak OpenFGA Event Publisher (SPI)
Keycloak has a series of Java Service Provider Interfaces (SPI) that allows adding new functionalities where needed. The main purpose of this SPI is to listen to the Keycloak events and publish these events to an OpenFGA solution.
In this case, the extension listens to the Admin Events related to operation in Keycloak Identity, Role and Group model. So far, the extension proceeds with the following steps:
Step 1) Parse and enrich the default Keycloak events in the following cases:
Step 2) Transform the Keycloak event into a OpenFGA tuple key based on the Keycloak Authorization Model (see section OpenFGA - Keycloak Authorization Model) using the OpenFGA SDK:
Step 3) Publish the event to the OpenFGA solution:
Publishes the Tuple Key as ClientWriteRequest
object to the OpenFGA server over an HTTP request fgaClient.write(request)
with the OpenFGA SDK client.
Here is a high level overview of the extension:
The extension supports the discovery of the OpenFGA authorization model and uses it to determine which events are handled.
Full details of the SPI component and the configuration is available in the GitHub repo.
Use case: How Does It Work?
Overview
As an example, we will implement an Product Catalog web application that has the following requirements:
- Only authenticated user with MFA can access to the application
- Product can be viewed by their Analyst
- Product can be edited by their Admin
- Global Admin users can view or edit any Product
Involved Components
The PoC is available in the following GitHub repo:
This repo describes how to deploy and configure the following components (some of them were describe before) running as containers:
Core:
- Keycloak is responsible for handling the authentication with the standard OpenID Connect and is managing the user access with his Role Model
- Keycloak is configure with a custom extension keycloak-openfga-event-publisher which listens to the Keycloak events, parses this event into an OpenFGA tuple key and publishes the event to OpenFGA.
- OpenFGA is configured with the Keycloak Authorization Model in the store called “keycloak”
Apps:
- Product catalog web application is integrated with Keycloak to authenticate the users
- API product catalog is protected by OAuth and it utilizes the OpenFGA SDK to enforce relationship-based access control (ReBAC)
All the components are detailed below:
Step 1) Deploy and configure the PoC
Follow the instructions detailed in the github repo keycloak-openfga-workshop to deploy and configure the components.
As the result you will have up and running all the components described before. You will also have created all the users and role model, here are detailed below:
Roles:
- Admin Catalog role (admin-catalog) is related to the roles: View (view-product) and Edit product (edit-product)
- Analyst Catalog role (analyst-catalog) is related to the role View product (view-product)
Users:
Once these steps are finished, the Keycloak OpenFGA Event Listener extension has to proceed to publish these events to the OpenFGA server using the SDK. Here are all tuples stored
The users are identified by the value of the claim sub in the OpenFGA Playground, see the Tuples tab.
Step 2.1) Try it Out: Check User Access thru OpenFGA Playground
The OpenFGA Playground can be accessed by opening a browser to http://localhost:3000/playground
Click the Tuple Queries > section below the Types Previewer. Enter the following in the query prompt field and press the Enter key:
In this case, OpenFGA is evaluating the relationships and evaluating true or false responses according to the authorization model and relationship tuples.
For instance, the assertion #4 is True because Paula has implied relationships to the view-product role for having a direct relationship to the Analyst role.
On the other hand, the assertion #3 is False because Paula does not have the edit-product role.
Step 2) Try it Out: Run the full demo
Here is an overview of the user cases.
Use case 1: Access to the Store for managing products as an Analyst (Paula)
- Access to the Store web application and proceed to login with Paula (paula / demo1234!) in Keycloak
2.1 Keycloak will return the id_token and the access token to the application
3. The Store web application will show the product catalog
3.1 The Store web application will call the product API sending the access token in the Authorization header
3.2 The API will apply the following steps:
3.3. It will validate the token following the OAuth 2.0 standard and it will extract the claim sub to identify the user
3.4. Then it will call the OpenFGA API to check if the user has the view-product role with the relationship assignee
3.5. OpenFGA will return the response “allowed”
3.6. The API will return the product information and the store app will show the information to the user
4. Try to publish a product by clicking the button “Publish” but you will see that Paula is not allowed
4.1. The app will call the product API sending the access token in the Authorization header
4.2. The API will apply the following steps:
4.3. It will validate the token following the OAuth 2.0 standard and it will extract the claim sub to identify the user
4.4. Then it will call the OpenFGA API to check if the user has the edit-product role with the relationship assignee
4.5. OpenFGA will return the response “denied” and the app will show the error message “Unauthorized Access”.
Use case 2: Access to the Store for managing products as an Admin (Richard)
- Access to the Store web application and proceed to login with Richard (richard / demo1234!) in Keycloak
2. Keycloak will return the id_token and the access token to the application
3. The store web application will show the product catalog
4. Try to publish a product by clicking the button “Publish” but you will see that Richard is allowed
Use case 3: Access to the Store for managing products as an Regular User (Peter)
- Access to the Store web application and proceed to login with Peter (peter / demo1234!) in Keycloak
2. Keycloak will return the id_token and the access token to the application
3. The store web application will try to the products show but the user is not allowed
All the use cases are available here.
Conclusions
I hope you’ve enjoyed the article. In my opinion, this approach solves several problems in the modern applications which have evolved and this makes fine-grained authorization a critical element in software. As I described in the article, the API is still protected by OAuth 2.0, since it requires an access token for accessing it. But the complex authorization rules are implemented into OpenFGA.
Another point to have in mind is that nowadays it is all about scalability, so decoupling the authorization part from Keycloak and delegating it to OpenFGA (which was designed JUST for handling authorization at scale) it’s a good path to follow. Another thing to bear in mind is that the direct integration between AM and OpenFGA simplifies the authorization architecture. With the use of the SDK in the extension, it provides the possibility to adapt the integration based on the authorization model.
Finally, this authorization architecture can evolve in many different ways, for instance, adding new components acting as PEPs (Proxy, API Gateway, etc) working in conjunction or not with OPA (Open Policy Agent), etc. Taking these points into consideration, I will continue talking about how to improve this for sure
Please stay connected to get the updates if you are interested in this topic !
That’s all for this post!
Thank you for reading. If you like it, then share it!
Reference:
- Workshop: https://github.com/embesozzi/keycloak-openfga-workshop
- Keycloak OpenFGA Event Publisher (new): https://github.com/embesozzi/keycloak-openfga-event-publisher
- Keycloak OpenFGA Event Kafka: https://github.com/embesozzi/keycloak-openfga-event-kafka (this was used in the first version of the PoC if you want to integrate the AM and OpenFGA server through Kafka)