How to authorize your .NET Web APIs using Keycloak
1 Mastering API Access Control with Keycloak
This article builds upon the insightful work of my colleague Sebastian Fulga; you can read his original piece here.
When it comes to securing Web APIs, authentication is only half the story. Authorization — knowing who can do what is just as critical, yet often receives less attention. In this follow-up, I’ll walk you through implementing fine-grained authorization using Keycloak, empowering you to manage access precisely. Whether you're just starting with authorization or already working with Keycloak, these steps are clear, practical, and designed with developers in mind.
Keycloak is an open-source single sign-on (SSO) solution licensed under Apache-2.0. Its implementation is publicly available on GitHub. Keycloak offers robust features including strong authentication, user management, identity provider integration, user federation, session control, event logging, and more. It’s highly configurable and has detailed official documentation on its website.

2 Setting Up Keycloak for Fine-Grained Authorization
If you’ve already installed and configured Keycloak using the Docker file mentioned in Sebastian Fulga’s original article, you can begin working with your Keycloak server.
In this guide, we'll use the default master realm for simplicity. However, you’re free to create a new realm if preferred, this won’t impact the core behavior, but it will result in URL paths different from those shown here.
Once deployed, Keycloak should be accessible at: http://localhost:7011/
You can log in using the admin credentials defined in your Docker Compose file.
Important Version Note:
At the time of writing, the current Keycloak version is 26.2.5. Future versions may introduce interface changes or new features, so always refer to the official Keycloak documentation and release notes to stay updated. It's best practice to use the latest stable version to take advantage of ongoing improvements from the development team.
2.1 Create New User


After creating a new user in Keycloak, the next step is to set up user credentials. To do this, navigate to the "Credentials" tab within the user's detail page in the Keycloak admin console.

2.2 Creating a New Client and Enabling Authorization in Keycloak
Now that you’ve created a user, the next step is to create a new client in Keycloak and enable the Authorization feature. Creating a client is very similar to creating a user.
To get started, navigate to the “Clients” menu in the Keycloak admin console and click the “Create Client” button.
This client represents the application or backend service that will authenticate via the Keycloak API. In our example, we’ve named the client MyApp, but you can choose any name that fits your project.
Here are the essential settings to configure for the client:
- Client Authentication: ON
- Authorization: ON
- Direct Access Grant: Enabled
These settings ensure that your client is fully equipped to handle secure authentication and fine-grained authorization using Keycloak.

2.3 Creating User Roles for Your Keycloak Client
For our MyApp client, we need to define user roles. Keycloak has two role types: realm roles and client roles.
- Realm roles can be assigned across the entire realm and all clients.
- Client roles are limited to the specific client where they are created.
In this tutorial, we’ll use client roles. To create them, go to the Roles tab under your client’s details page.
We’ll apply the Role-Based Access Control (RBAC) approach to show Keycloak’s flexibility in handling fine-grained permissions.
Create the following roles, which will later be used to control access to backend APIs:

2.4 Configure client Authorization
To enable fine-grained access control, navigate to the Authorization tab under the MyApp client page, just as you did for roles.
Keycloak may auto-generate default resources, policies, and permissions. These can be safely deleted to keep your configuration clean. While setting up the Authorization module may take some time, this guide will walk you through each step.
Configure Scopes
Scopes define the specific actions a user can perform and are essential for fine-grained authorization in Keycloak. In this example, we’ll use standard scopes like:
- manage
- query
- view
You can also define custom scopes to match your application's needs.

Configure Resources
Resources represent APIs or features you want to protect. These can range from single endpoints to entire application modules.
In this setup, we’ll create two resources:
- users – linked to the query, view, and manage scopes
- fetch-weather – without any scope, to demonstrate resource-based permissions
Note: Resources in Keycloak can have one, many, or no scopes.


Configure Policies
Policies in Keycloak define the rules that must be satisfied to grant access to a resource. They are evaluated during the authorization process and determine whether a user can access a specific resource.
Policies can be based on various conditions, such as:
- User roles
- Client applications
- User groups
- Attributes or context (e.g., time, regex patterns)
In this guide, we’ll create role-based policies for each of the client roles (user, admin, manager) defined earlier.
These policies will form the foundation for assigning permissions to protected resources.

There are a few key aspects to understand when configuring role-based policies in Keycloak:
- Multiple Roles: A single policy can include various roles. The "Required" checkbox determines whether a role must be present for access. In our case, since each policy includes just one role, it will be required by default. For multi-role policies, be sure to define which roles are mandatory.
- Fetch Roles: This option is disabled by default. When enabled, Keycloak fetches roles in real time from the database rather than relying solely on the roles present in the access token. This can be powerful for dynamic role evaluation. If left disabled, users will retain the roles from their current token until a new token is issued.
- Logic Setting: The "Logic" configuration controls how decisions are applied:
- Positive: The policy behaves as defined, permits access, and denies blocks it.
- Negative: The result is inverted, the permit becomes denied, and vice versa.
By the end of this section, you should have a complete list of role-based policies, each linked to one of the roles created earlier.

Configure Permissions
The final step in setting up Keycloak fine-grained authorization is to configure permissions. This step must come last, as it depends on previously defined resources, scopes, and policies.
Permissions control what actions a user or client can perform on specific resources or scopes. They are a key part of Keycloak's authorization module, enabling precise access control by linking resources to the policies that govern access decisions.
Keycloak supports two types of permissions:
- Scope-based permissions – applied to specific actions or scopes
- Resource-based permissions – applied directly to a resource
For this demo, we’ll use both types:
- Scope-Based Permissions:
- permission_query-users
- permission_view-users
- permission_manage-users
- Resource-Based Permission:
- permission_fetch-weather

There are a few key points worth noting:
- Permissions can simultaneously support one or more 'Authorization scopes'.
- Similarly, multiple 'Policies' can be backed by Permissions at the same time.
- The 'Decision strategy' dictates how the policies associated with a given permission are evaluated and how a final decision is reached. Here are the options:
- 'Affirmative' implies that at least one policy must be positive.
- 'Unanimous' means that all policies must be favorable.
- 'Consensus' means that the number of positive outcomes must outweigh the number of negative ones.
In the end we will have the following permissions:

We observe that we have one permission of the type 'Resource-Based'. This permission is much like the others, except that the scope is not a prerequisite.
Now that we have everything in place, we can delve into the code to investigate how we handle authorization. However, before we do that, roles must be assigned to our user; otherwise, we'll lack access to our APIs.
Navigate to the Users tab in Keycloak, and then proceed to our user to assign all the roles from our MyApp client, just as illustrated:

3 How to Implement Fine-Grained Access Control with Keycloak
Let's start with the project structure, although this is not very important since you can create your own file structure. Since this is just a demo project, we will keep it simple.

Dependencies required from NuGet gallery:
- Microsoft.AspNetCore.Authentication.JwtBearer
Appsettings.json:

To validate JSON Web Tokens (JWT) in your ASP.NET Core application, you typically configure a JwtSettings section in appsettings.json. When using Keycloak, you don't need to set a secret key manually. Instead, Keycloak simplifies JWT signature verification by exposing a MetadataAddress endpoint.
This metadata endpoint (/.well-known/openid-configuration) provides Keycloak’s OpenID Connect configuration. It includes essential information such as:
- The issuer (iss)
- Supported scopes and response types
- Authorization and token endpoints
- Public keys for JWT signature validation
- Logout endpoints and supported algorithms
With this built-in support, Keycloak enables secure and standardized token validation with minimal manual setup.
Next, you’ll define a custom authorization requirement by creating a new class: PermissionRequirement.cs. This record should implement Microsoft's IAuthorizationRequirement interface.AspNetCore.Authorization namespace.
This class forms the foundation for a custom authorization policy, allowing you to enforce fine-grained access control based on your application’s logic, such as user roles, scopes, or specific permissions.

Then we need to create a new file named ApplicationPolicies.cs that will contain two classes:

In our code, we have defined a static class known as DependencyInjection. The purpose of this class is to house our helper methods for dependency injection. These helper methods are designed as extension methods and these are being invoked in the Program.cs file.

In this section, we're setting up Authentication and Authorization for our service. We're also adding http clients. As an extra step, we're setting up a reverse proxy, which we'll discuss in more detail later.
We need to specify the previously defined necessary policies within the AddAuthorization method.
For simplicity, we stick with minimal APIs, which are the examples in the Program.cs file:

All API requests in this demo are GET requests, designed to return simple messages that confirm Keycloak authorization is functioning correctly.
Protected Endpoints and Required Policies:
- /api/users → Requires the QueryUsers policy
- /api/users/{id} → Requires the ViewUsers policy
- /api/users/manage → Requires the ManageUsers policy
To enforce these policies, we’ve implemented a custom authorization handler called RequestBasedHandler. This handler sends a request to Keycloak on each API call to:
- Verify the user's authentication
- Validate the applied authorization policy
In our setup, the client (we’re using Postman) sends API requests with the Keycloak-issued access token, retrieved from: http://localhost:7011/realms/master/protocol/openid-connect/token.
This ensures each API request is checked in real time against the latest Keycloak roles and permissions.
RequestBasedHandler:

Let's now authenticate and get a new access token from Keycloak:
<pre><code class="language-bash">
curl --location 'http://localhost:7011/realms/master/protocol/openid-connect/token/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIx...' \
--data-urlencode 'client_id=MyApp' \
--data-urlencode 'audience=MyApp' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:uma-ticket' \
--data-urlencode 'response_mode=permissions'
</code></pre>
This request returns a JSON object containing the list of all authorized resources and associated scopes for the authenticated user. This is useful for debugging, auditing, or dynamically adapting frontend behavior based on user permissions.
[ { "rsid": "0d7d72a8-8cc8-47b0-a38b-b973d9b197da", "rsname": "fetch weather" }, { "scopes": [ "view", "manage" ], "rsid": "38ffed86-d128-4eff-a1f9-261727c9548e", "rsname": "users" } ]