ZITADEL and Fine-Grained Authorization: A Code-Focused Exploration

Introduction

The current technological landscape has sparked a substantial shift in security strategies, steering us away from traditional perimeter-based models and towards a zero-trust approach. The embrace of multi-cloud infrastructures and remote work has further catalyzed this transition. An essential part of the shift to zero trust that often goes undiscussed is the move from coarse-grained to fine-grained security.

ZITADEL, a global and scalable identity and access management platform, provides robust default capabilities for managing access control through Role-Based Access Control (RBAC) and Delegated Access, laying a solid foundation for efficient and scalable access management. Yet, as we journey towards a zero-trust mindset, the limitations of coarse-grained security become increasingly apparent. The traditional RBAC system may grant an editor the rights to modify all documents, but interconnected and dynamic work environments require more fine-grained control. What if we want to grant editors access to edit documents related only to a specific project, within a certain time frame, or access only to a specific document? This is where fine-grained authorization steps in.

Fine-grained authorization enables access decisions based on various attributes such as user roles, specific attributes, actions they are trying to perform, and even contextual factors such as the time or location of access. Such nuanced access control is critical in modern applications.

In this article, we will delve into how ZITADEL responds to the growing demand for fine-grained authorization patterns. By leveraging ZITADEL's roles, meta-data, and actions, you can achieve a level of granularity and precision in access control that meets the demands of a zero-trust environment. Furthermore, ZITADEL can also integrate with external authorization services. Let’s explore!

The Need for Fine-Grained Authorization and its Potential Benefits

fine-grained-auth

Fine-grained authorization goes beyond traditional access control models by allowing access decisions to be based on more specific and varied factors. For instance, access might be granted based on the user's specific attributes, the resource they're trying to access, their location, the time of day, or any combination of these and more.

The key benefits of implementing fine-grained authorization are enhanced security, greater flexibility, regulatory compliance, and improved auditability. Thus, adopting a fine-grained authorization strategy can offer both enhanced security and operational advantages.

Decoding Fine-Grained Authorization Techniques

Implementing fine-grained authorization isn't a one-size-fits-all proposition, and each organization may require a unique approach based on its specific use cases, existing infrastructure, and security requirements. Nonetheless, several common techniques and methodologies serve as the foundation of most fine-grained authorization strategies. Here, we'll decode some of these essential techniques.

  • Policy-Based Access Control (PBAC): A powerful tool in the fine-grained authorization arsenal is PBAC. This approach uses specific policies, typically written in declarative language, to decide whether a user or system should have access to a particular resource. Policies can factor in an extensive range of conditions, providing a granular level of control.

  • ** Attribute-Based Access Control (ABAC)**: This technique expands upon the traditional role-based access control (RBAC) method. In ABAC, permissions are assigned based on user attributes, such as department or job title. This model provides more flexibility, as access decisions can be made based on a variety of user attributes and environmental conditions.

  • Relationship-Based Access Control (ReBAC): ReBAC is a recent evolution in access control models. It introduces the concept of relationships between subjects (users or groups) and objects (resources), allowing for even more specific access controls. This model can provide a powerful tool for multi-tenant applications, where resources need to be shared based on complex relationships.

  • Risk-based Access or Conditional Access: Some applications may need to alter their behavior based on where a user is logged in from, what region the application is running in, or the day/time an operation is invoked. This is often due to regional differences in service levels or compliance requirements.

  • Fine-Grained Security Logging and Events: The evolution towards fine-grained security also applies to security logging. Instead of merely recording logins, it's now crucial to log every access decision an application makes during a user's session. This helps identify and rectify over-provisioned or misconfigured permissions more effectively.

ZITADEL’s Current Authorization Mechanisms

ZITADEL is a cloud-native Identity and Access Management solution (IAM) that provides various security mechanisms to secure applications and services. It employs a range of different authorization strategies, including Role-Based Access Control (RBAC) and Delegated Access.

Role-Based Access Control (RBAC) and Delegated Access

ZITADEL employs RBAC as a primary mechanism for assigning and managing user permissions. In RBAC, permissions are associated with roles, and users are assigned to these roles. For example, you have the option to enforce access to resources by defining roles in the form of functional user roles (e.g., admin, editor, reader) or related to an application feature (e.g., book:read, book:edit). This approach provides a simple way to manage user access based on their roles within an organization.

The delegated access feature further extends this capability by allowing roles to be delegated to other organizations. This means you can provide an external organization with certain permissions within your system. This is particularly useful for organizations that work closely together or have a hierarchical structure.

These features provide a strong base for controlling access in various contexts. However, they may not be sufficient or can get too complicated for more complex authorization requirements that require a finer level of control, which leads us to the topic of implementing fine-grained authorization in ZITADEL.

Extending ZITADEL's Existing Capabilities for Fine-Grained Access Control

Despite the comprehensive features that come with ZITADEL, there may be instances where a more customized or fine-grained approach is needed. Currently, the most effective way to implement fine-grained authorization in ZITADEL is by using custom application logic for smaller projects, or for larger scale projects, leveraging an available third-party tool such as warrant.dev, cerbos.dev, etc. These tools can integrate with ZITADEL, further enhancing your capacity for nuanced, fine-grained authorization.

Leverage the Actions Feature, Custom Metadata, and Claims for ABAC

ZITADEL goes beyond the static nature of RBAC with its dynamic Actions feature, designed to facilitate the implementation of attribute-based access control (ABAC). In contrast to RBAC, which assigns access rights based on a user's role, ABAC is a more flexible method that evaluates a set of attributes associated with the user, action, and resource for each access request.

In ZITADEL, the Actions feature can be used to create a post-authentication script that checks for specific user attributes and denies access if necessary. You can also use Actions to create custom claims that can further enhance your ABAC system. This powerful feature opens up the possibility for advanced authorization models, such as denying access to a resource based on the user's location, time of access, or any attribute that you can define and evaluate.

ZITADEL allows administrators or authorized developers to set custom metadata on users and organizations, further enhancing your ability to create fine-grained access control. Even aggregated claims are possible, where you request additional information from a third-party system and return the claims in the user information. This could be information from a CRM system, a training or certification tool, an HR system, or any other relevant source. Moreover, ZITADEL can handle application-specific resources such as shipping orders, IoT devices, and documents, and manage the policies defining access to these resources based on certain attributes. These attributes include User-Sub, Roles, Claims, IP, Tenant/Organization, and others.

A Use Case

The roles in a media company

In the fast-paced world of digital media, ensuring the right access to the right resources for the right employees is crucial. For a media company employing a host of journalists and editors, each with differing levels of experience and responsibility, the challenge lies in effectively managing access rights across various applications.

Let's consider such a media house. Here, the primary roles are journalists, who write articles, and editors, who review, edit, and publish these articles. The employees are further categorized based on their experience levels: junior, intermediate, and senior.

Let’s assume the media house has two primary applications in use. The first is a Newsroom Application, where journalists write their articles, and editors review, edit, and publish them. The second application is an HR Application. This handles all employee-related data and tracks their progress within the organization, including promotions and experience level changes. For example, when a journalist gets promoted from junior to intermediate, this change is recorded in the HR application. Access to certain features in the Newsroom Application needs to be strictly controlled. Junior journalists should only write articles, intermediate and senior journalists should be able to review articles, and only editors, particularly those with higher experience levels, should have the rights to edit and publish articles.

To manage these varied and complex access requirements, the media house utilizes ZITADEL, which they use for their general identity and access management needs. The goal here is to use ZITADEL's capabilities to ensure that access rights are correctly attributed according to both an employee's role and the seniority level, providing a clear separation of responsibilities within the media house.

Let's see how we can create a Flask API that upholds these access controls and how to configure ZITADEL to handle these authorization needs.

The Application

Let’s assume that the main operational application within the media company is the Newsroom Application, a Flask-based API. The Flask API acts as the primary interface where journalists write articles and editors review, edit, and publish these articles. The API has been designed with specific endpoints that correspond to these permissions, enforcing role and experience-based access control.

Here are the primary endpoints that map to the permissions:

  • write_article: This resource endpoint is exclusively for journalists to write articles. All journalists, regardless of their experience level, have access to this resource.
  • edit_article: This resource endpoint is specifically for editors. It enables them to edit the articles written by the journalists.
  • review_articles: This resource endpoint provides access to review articles, which is a responsibility given to senior journalists, and both intermediate and senior editors.
  • publish_article: This resource endpoint is for publishing articles, a task designated to intermediate and senior journalists, and senior editors.

Each of these endpoints is accessible based on a combination of role and experience level, ensuring the right people have the right access.

Under the hood, the Flask API uses JWT (JSON Web Tokens) for authentication and authorization. When a user makes a request, they must provide a valid JWT as part of the request's Authorization header. This token is then decoded to verify its validity and to extract any custom claims.

Custom claims are key to this use case. They contain additional information about the user, in this case, the user's role and experience level. When a request is made to the API, these claims are extracted from the JWT and used to determine if the user has the necessary permissions to access the requested resource.

Configure ZITADEL and Create the API

You can find all source files and instructions here.

The Application Logic

Application Flow

Figure 1: Interactions showcasing the application of fine-grained authorization in a real-world Newsroom Application

User Role Assignment at Onboarding: As part of the user onboarding process, each user is assigned a role. The role could be that of a journalist or editor. This role assignment is crucial as it is the basis for access control in our system.

Experience Level Management by HR app: Beyond the user role, the system also considers the user's experience level. The experience level (junior, intermediate, senior) is determined and managed by an HR application. When a change occurs in a user's experience level, it is updated as metadata in ZITADEL. It's worth noting that the system assumes a 'junior' level by default when no experience level is specified in the token claims.

Token Validation: auth.py is responsible for token validation. When a request hits the API, auth.py validates the token by calling ZITADEL's token introspection endpoint. If the token is valid and active, it is then decoded and printed for inspection. The decoded token (containing the user's role and experience level) is then made available globally using the Flask g object.

Note: Although JWTs can be validated locally using JWKS, we chose to use ZITADEL's token introspection endpoint for enhanced security and real-time token validity. This approach enables immediate revocation, simplifies implementation by centralizing control, and reduces the risk of security vulnerabilities. This helps ensure our API's authentication and authorization processes remain robust, accurate, and in sync with the current server state.

Fine-Grained Access Control: The Python Flask application is responsible for authorizing access to resources based on a user's role and experience level. It leverages a predefined access control list that maps each resource endpoint to the user roles and experience levels authorized to access them. This list serves as the rulebook for granting or denying access to resources. Each time a user attempts to access a resource, the function authorize_access checks if the user's role and experience level meet the access requirements for the requested endpoint. If they do, access is granted; if not, the function returns a message stating that access is denied, with details of the user's role and experience level and the resource they attempted to access.

Business Logic: app.py is the main Flask application file. It defines the resource endpoints and applies the token_required decorator (from auth.py) to secure them. Each time a request is made to these secured endpoints, the token_required decorator first verifies the token. Then, the authorize_access function (from access_control.py) is called to check if the user is authorized to access the endpoint based on their role and experience level. Separation of Concerns: In the design of this API, special attention was given to ensuring that business logic and access control rules are cleanly separated. This is crucial for the maintainability and scalability of the application.

The business logic is independent of who is allowed to access these endpoints, thus it is concerned only with performing actions rather than checking who can trigger these actions. access_control.py is strictly focused on implementing fine-grained access control rules. It does not concern itself with the specifics of how requests are processed, but only with who has the permission to make these requests. These rules are based on roles and experience levels stored in JWT token claims. The authorize_access function in access_control.py is used in the business logic layer (app.py ) to guard the endpoints.

The separation of business logic and access control rules allows for a clean, modular design where changes to business processes and access rules can be made independently of each other. This increases the maintainability of the code and makes it easier to manage as the application scales. Additionally, this design makes the system more secure as access rules are abstracted away from the main business logic, reducing the risk of accidentally introducing security vulnerabilities when modifying the business logic.

Integrate with an External Authorization Service

ZITADEL allows integration with external authorization services such as warrant.dev, cerbos.dev, or essentially any service that can consume ZITADEL's users and roles. By using an external authorization service, you have the flexibility to create a fine-tuned authorization strategy that precisely meets your organization's needs. This could involve creating complex policies that dictate access based on a variety of user attributes and conditions, extending far beyond the traditional roles used in RBAC.

Instead of enforcing a one-size-fits-all solution, ZITADEL believes in giving users the tools and options to construct an access control framework that perfectly suits their organization's unique needs and challenges. This emphasis on flexibility ensures that as those needs evolve, the access control strategy can adapt seamlessly.

Liked it? Share it!