Keycloak as Authorization Server in .NET
Tags: dotnet, aspnetcore, csharp, keycloak
Published: 2022-12-29 14:40:18

---
title: Keycloak as Authorization Server in .NET
published: true
date: 2022-12-28 00:00:00 UTC
tags: dotnet, aspnetcore, csharp, keycloak
canonical_url: https://nikiforovall.github.io/dotnet/keycloak/2022/12/28/keycloak-authorization-server.html
---
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myh7l1e3g2xet78kpd3x.png)

- [TL;DR](#tldr)
- [Introduction](#introduction)
- [Example Overview](#example-overview)
  - [Configure Keycloak](#configure-keycloak)
  - [Authorization based on ASP.NET Core Identity roles](#authorization-based-on-aspnet-core-identity-roles)
  - [Authorization based on Keycloak realm and client roles](#authorization-based-on-keycloak-realm-and-client-roles)
  - [Authorization based on Authorization Server permissions](#authorization-based-on-authorization-server-permissions)
- [The power of Authorization Server - define policies and permissions](#the-power-of-authorization-server---define-policies-and-permissions)
  - [Evaluate permissions](#evaluate-permissions)
  - [Demo](#demo)
- [Summary](#summary)
- [References](#references)

## TL;DR

`Keycloak.AuthService.Authorization` provides a toolkit to use Keycloak as Authorization Server. An authorization Server is a powerful abstraction that allows to control authorization concerns. An authorization Server is also advantageous in microservices scenario because it serves as a centralized place for IAM and access control.

`Keycloak.AuthServices.Authorization`: [https://github.com/NikiforovAll/keycloak-authorization-services-dotnet#keycloakauthservicesauthorization](https://github.com/NikiforovAll/keycloak-authorization-services-dotnet#keycloakauthservicesauthorization)

Example source code: [https://github.com/NikiforovAll/keycloak-authorization-services-dotnet/tree/main/samples/AuthZGettingStarted](https://github.com/NikiforovAll/keycloak-authorization-services-dotnet/tree/main/samples/AuthZGettingStarted)

## Introduction

Authorization refers to the process that determines what a user is able to do.ASP.NET Core authorization provides a simple, declarative role and a rich policy-based model. Authorization is expressed in requirements, and handlers evaluate a user’s claims against requirements. Imperative checks can be based on simple policies or policies which evaluate both the user identity and properties of the resource that the user is attempting to access.

Resource servers (applications or services serving protected resources) usually rely on some kind of information to decide if access should be granted to a protected resource. For RESTful-based resource servers, that information is usually obtained from a security token, usually sent as a bearer token on every request to the server. For web applications that rely on a session to authenticate users, that information is usually stored in a user’s session and retrieved from there for each request.

Keycloak is based on a set of administrative UIs and a RESTful API, and provides the necessary means to create permissions for your protected resources and scopes, associate those permissions with authorization policies, and enforce authorization decisions in your applications and services.

Considering that today we need to consider heterogeneous environments where users are distributed across different regions, with different local policies, using different devices, and with a high demand for information sharing, Keycloak Authorization Services can help you improve the authorization capabilities of your applications and services by providing:

- Resource protection using fine-grained authorization policies and different access control mechanisms
- Centralized Resource, Permission, and Policy Management
- Centralized Policy Decision Point
- REST security based on a set of REST-based authorization services
- Authorization workflows and User-Managed Access
- The infrastructure to help avoid code replication across projects (and redeploys) and quickly adapt to changes in your security requirements.

## Example Overview

In this blog post I will demonstrate how to perform authorization in two ways:

- Role-based access control (RBAC) check executed by _Resource Server (API)_
  - `/endpoint1` - required ASP.NET Core identity role
  - `/endpoint2` - required realm role
  - `/endpoint3` - required client role
- Remote authorization policy check executed by _Authorization Server (Keycloak)_
  - `/endpoint4` - remotely executed policy selected for “workspace” - resource, “workspaces:read” - scope.

```csharp
var app = builder.Build();

app
    .UseHttpsRedirection()
    .UseApplicationSwagger(configuration)
    .UseAuthentication()
    .UseAuthorization();

app.MapGet("/endpoint1", (ClaimsPrincipal user) => user)
    .RequireAuthorization(RequireAspNetCoreRole);

app.MapGet("/endpoint2", (ClaimsPrincipal user) => user)
    .RequireAuthorization(RequireRealmRole);

app.MapGet("/endpoint3", (ClaimsPrincipal user) => user)
    .RequireAuthorization(RequireClientRole);

app.MapGet("/endpoint4", (ClaimsPrincipal user) => user)
    .RequireAuthorization(RequireToBeInKeycloakGroupAsReader);

await app.RunAsync();

```

Project structure:

```
$ tree -L 2
.
├── AuthZGettingStarted.csproj
├── Program.cs
├── Properties
│ └── launchSettings.json
├── ServiceCollectionExtensions.Auth.cs
├── ServiceCollectionExtensions.Logging.cs
├── ServiceCollectionExtensions.OpenApi.cs
├── appsettings.Development.json
├── appsettings.json
├── assets
│ ├── realm-export.json
│ └── run.http
└── docker-compose.yml

```

Entry point:

```csharp
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
var services = builder.Services;

builder.AddSerilog();
services
    .AddApplicationSwagger(configuration)
    .AddAuth(configuration);

```

Register AuthN and AuthZ services in Dependency Injection container:

```csharp
public static IServiceCollection AddAuth(
    this IServiceCollection services, IConfiguration configuration)
{
    services.AddKeycloakAuthentication(configuration);

    services.AddAuthorization(options =>
    {
        options.AddPolicy(
            Policies.RequireAspNetCoreRole,
            builder => builder.RequireRole(Roles.AspNetCoreRole));

        options.AddPolicy(
            Policies.RequireRealmRole,
            builder => builder.RequireRealmRoles(Roles.RealmRole));

        options.AddPolicy(
            Policies.RequireClientRole,
            builder => builder.RequireResourceRoles(Roles.ClientRole));

        options.AddPolicy(
            Policies.RequireToBeInKeycloakGroupAsReader,
            builder => builder
                .RequireAuthenticatedUser()
                .RequireProtectedResource("workspace", "workspaces:read"));

    }).AddKeycloakAuthorization(configuration);

    return services;
}

public static class AuthorizationConstants
{
    public static class Roles
    {
        public const string AspNetCoreRole = "realm-role";

        public const string RealmRole = "realm-role";

        public const string ClientRole = "client-role";
    }

    public static class Policies
    {
        public const string RequireAspNetCoreRole = nameof(RequireAspNetCoreRole);

        public const string RequireRealmRole = nameof(RequireRealmRole);

        public const string RequireClientRole = nameof(RequireClientRole);

        public const string RequireToBeInKeycloakGroupAsReader = 
            nameof(RequireToBeInKeycloakGroupAsReader);
    }
}

```

### Configure Keycloak

In this post I’m going to skip basic Keycloak installation and configuration, please see my previous posts for more details [https://nikiforovall.github.io/tags.html#keycloak-ref](https://nikiforovall.github.io/tags.html#keycloak-ref).

Prerequisites:

1. Create a realm named: **Test**
2. Create a user with username/password: **user/user**
3. Create a realm role: **realm-role**
4. Create a client: **test-client**
  1. Create an audience mapper: **Audience** ➡ **test-client**
  2. Enable **Client Authentication** and **Authorization**
  3. Enable **Implicit flow** and add a valid redirect URL (used by Swagger to retrieve a token)
5. Create a client role: **client-role**
6. Create a group called **workspace** and add the “user” to it

Full Keycloak configuration (including the steps below) can be found at [realm-export.json](https://gist.github.com/NikiforovAll/c5524c3f1dac9249935d2a6e84b532fd)

### Authorization based on ASP.NET Core Identity roles

`Keycloak.AuthService.Authentication` ads the `KeycloakRolesClaimsTransformation` that maps roles provided by Keycloak. The source for `role` claim could be one of the following:

- Realm - map realm roles
- ResourceAccess - map client roles
- None - don’t map

Depending on your needs, you can use realm roles, client roles or skip automatic role mapping/transformation. The role claims transformation is based on the config. For example, here is how to use realms role for ASP.NET Core Identity roles. As result, you can use build-in [role-based authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles).

```json
{
  "Keycloak": {
    "realm": "Test",
    "auth-server-url": "http://localhost:8080/",
    "ssl-required": "none",
    "resource": "test-client",
    "verify-token-audience": true,
    "credentials": {
      "secret": ""
    },
    "confidential-port": 0,
    "RolesSource": "Realm"
  }
}

```

So, for a user with the next access token generated by Keycloak the roles are effectively evaluated to “realm-role”, “default-roles-test”, “offline\_access”, “uma\_authorization”. And if you change “RolesSource” to “ResourceAccess” it would be “client-role”.

```json
{
  "exp": 1672275584,
  "iat": 1672275284,
  "jti": "1ce527e6-b852-48e9-b27b-ed8cc01cf518",
  "iss": "http://localhost:8080/realms/Test",
  "aud": [
    "test-client",
    "account"
  ],
  "sub": "8fd9060e-9e3f-4107-94f6-6c3a242fb91a",
  "typ": "Bearer",
  "azp": "test-client",
  "session_state": "c32e4165-f9bd-4d4c-93bd-3847f4ffc697",
  "acr": "1",
  "realm_access": {
    "roles": [
      "realm-role",
      "default-roles-test",
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "test-client": {
      "roles": [
        "client-role"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid email profile",
  "sid": "c32e4165-f9bd-4d4c-93bd-3847f4ffc697",
  "email_verified": false,
  "preferred_username": "user",
  "given_name": "",
  "family_name": ""
}

```

💡 Note, you can change “RolesSource” to “None” and instead of using `KeycloakRolesClaimsTransformation`, use Keycloak role claim mapper and populate `role` claim based on configuration. Luckily, it is easy to do from Keycloak admin panel.

```csharp
services.AddAuthorization(options =>
{
    options.AddPolicy(
        Policies.RequireAspNetCoreRole,
        builder => builder.RequireRole(Roles.AspNetCoreRole))
});

```

### Authorization based on Keycloak realm and client roles

`AuthorizationPolicyBuilder` allows to register policies and `Keycloak.AuthServices.Authorization` adds a handy method to register rules that make use of the specific structure of access tokens generated by Keycloak.

```csharp
services.AddAuthorization(options =>
{
    options.AddPolicy(
        Policies.RequireRealmRole,
        builder => builder.RequireRealmRoles(Roles.RealmRole));

    options.AddPolicy(
        Policies.RequireClientRole,
        builder => builder.RequireResourceRoles(Roles.ClientRole));
});

// PoliciesBuilderExtensions.cs
public static AuthorizationPolicyBuilder RequireResourceRoles(
    this AuthorizationPolicyBuilder builder, params string[] roles) =>
    builder
        .RequireClaim(KeycloakConstants.ResourceAccessClaimType)
        .AddRequirements(new ResourceAccessRequirement(default, roles));

public static AuthorizationPolicyBuilder RequireRealmRoles(
    this AuthorizationPolicyBuilder builder, params string[] roles) =>
    builder
        .RequireClaim(KeycloakConstants.RealmAccessClaimType)
        .AddRequirements(new RealmAccessRequirement(roles));

```

### Authorization based on Authorization Server permissions

Policy Enforcement Point (PEP) is responsible for enforcing access decisions from the Keycloak server where these decisions are taken by evaluating the policies associated with a protected resource. It acts as a filter or interceptor in your application in order to check whether or not a particular request to a protected resource can be fulfilled based on the permissions granted by these decisions.

Keycloak supports fine-grained authorization policies and is able to combine different access control mechanisms such as:

- Attribute-based access control (ABAC)
- Role-based access control (RBAC)
- User-based access control (UBAC)
- Context-based access control (CBAC)
- Rule-based access control
- Using JavaScript
- Time-based access control
- Support for custom access control mechanisms (ACMs) through a Service Provider Interface (SPI)

Here is what happens when authenticated user tries to access a protected resource:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tc0qxn3y271umpiae7wg.png)

```csharp
services.AddAuthorization(options =>
{
    options.AddPolicy(
        Policies.RequireToBeInKeycloakGroupAsReader,
        builder => builder
            .RequireAuthenticatedUser()
            .RequireProtectedResource("workspace", "workspaces:read"));
});

// PoliciesBuilderExtensions.cs
/// <summary>
/// Adds protected resource requirement to builder.
/// Makes outgoing HTTP requests to Authorization Server.
/// </summary>
public static AuthorizationPolicyBuilder RequireProtectedResource(
    this AuthorizationPolicyBuilder builder, string resource, string scope) =>
    builder.AddRequirements(new DecisionRequirement(resource, scope));

```

## The power of Authorization Server - define policies and permissions

Resource management is straightforward and generic. After creating a resource server, you can start creating the resources and scopes that you want to protect. Resources and scopes can be managed by navigating to the Resource and Authorization Scopes tabs, respectively.

So, to define a protected resource we need to create it in the Keycloak and assigned scope to it. In our case, we need to create “workspace” resource with “workspaces:read” scope.

For more details, please see [https://www.keycloak.org/docs/latest/authorization\_services/#\_resource\_overview](https://www.keycloak.org/docs/latest/authorization_services/#_resource_overview).

 

To create a scope:

1. Navigate to “Clients” tab on the sidebar
2. Select “test-client” from the list
3. Go to “Authorization” tab (make sure you enabled “Authorization” checkbox on the “Settings” tab)
4. Select “Scopes” sub-tab
5. Click “Create authorization scope”
6. Specify **workspaces:read** as Name
7. Click “Save”

To create a resource:

1. From the “Authorization” tab
2. Select “Resources” sub-tab
3. Click “Create resource”
4. Specify **workspace** as Name
5. Specify **urn:resource:workspace** as Type
6. Specify “workspaces:read” as “Authorization scopes”
7. Click “Save”

Let’s say we want to implement a rule that only users with **realm-role** role and membership in **workspace** group can read a “workspace” resource. To accomplish this, we need to create the next two policies:

1. From the “Authorization” tab
2. Select “Policies” sub-tab
3. Click “Create policy”
4. Select “Role” option
5. Specify **Is in realm-role** as Name
6. Click “Add roles”
7. Select **realm-role** role
8. Logic: **Positive**
9. Click “Save”
10. Click “Create policy”
11. Select “Group” option
12. Specify **Is in workspace group** as Name
13. Click “Add group”
14. Select “workspace” group
15. Logic: **Positive**
16. Click “Save”

Now, we can create the permission:

1. From the “Authorization” tab
2. Select “Permissions” sub-tab
3. Click “Create permission”
4. Select “Create resource-based permission”
5. Specify **Workspace Access** as Name
6. Specify **workspace** as resource
7. Add **workspaces:read** as authorization scope
8. Add two previously created policies to the “Policies”
9. Specify **Unanimous** as Decision Strategy
10. Click “Save”

The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. ‘Affirmative’ means that at least one policy must evaluate to a positive decision in order for the final decision to be also positive. ‘Unanimous’ means that all policies must evaluate to a positive decision in order for the final decision to be also positive. ‘Consensus’ means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative.

### Evaluate permissions

1. From the “Authorization” tab
2. Select “Evaluate” sub-tab

Let’s say the “user” has **realm-role** , but is not a member of **workspace** group

Here is how the permission evaluation is interpreted by Keycloak:

<center>
 <img src="https://nikiforovall.github.io/assets/keycloak-authz-server/evaluate1.png" alt="evaluate1">
</center>

And if we add the “user to **workspace** group:

<center>
 <img src="https://nikiforovall.github.io/assets/keycloak-authz-server/evaluate2.png" alt="evaluate1">
</center>
### Demo

1. Navigate at [https://localhost:7248/swagger/index.html](https://localhost:7248/swagger/index.html)
2. Click “Authorize”. Note, the access token is retrieved based on “Implicit Flow” that we’ve previously configured.
3. Enter credentials: “user/user”
4. Execute “/endpoint4”
<center>
 <img src="https://nikiforovall.github.io/assets/keycloak-authz-server/authz-demo2.png" alt="authz-demo2">
</center>

As you can see, the response is 200 OK. I suggest you to try removing “user” from the “workspace” group and see how it works.

As described above, the permission is evaluated by Keycloak therefore you can see outgoing HTTP requests in the logs:

```
12:19:28 [INFO] Start processing HTTP request "POST" http://localhost:8080/realms/Test/protocol/openid-connect/token
"System.Net.Http.HttpClient.IKeycloakProtectionClient.ClientHandler"
12:19:28 [INFO] Sending HTTP request "POST" http://localhost:8080/realms/Test/protocol/openid-connect/token
"System.Net.Http.HttpClient.IKeycloakProtectionClient.ClientHandler"
12:19:28 [INFO] Received HTTP response headers after 8.5669ms - 200
"System.Net.Http.HttpClient.IKeycloakProtectionClient.LogicalHandler"
12:19:28 [INFO] End processing HTTP request after 25.3503ms - 200
"Keycloak.AuthServices.Authorization.Requirements.DecisionRequirementHandler"
12:19:28 [DBUG] ["DecisionRequirement: workspace#workspaces:read"] Access outcome True for user "user"
"Microsoft.AspNetCore.Authorization.DefaultAuthorizationService"
12:19:28 [DBUG] Authorization was successful.

```

<center>
 <img src="https://nikiforovall.github.io/assets/keycloak-authz-server/authz-demo1.png" alt="authz-demo1">
</center>
* * *

💡 Note, In this post, I’ve showed you how to protect the one resource known to the system, but it is actually possible to create resources programmatically and compose ASP.NET Core policies during runtime. See `Keycloak.AuthServices.Authorization.ProtectedResourcePolicyProvider` for more details.

## Summary

An authorization Server is a highly beneficial abstraction and it is quite easy to solve a wide range of well-known problems without “Reinventing the wheel”. [Keycloak.AuthServices.Authorization](https://www.nuget.org/packages/Keycloak.AuthServices.Authorization) helps you to define a protected resource and does the interaction with Authorization Server for you. Let me know what you think 🙂

## References

- [https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction)
- [https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles)
- [https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies)
- [https://www.keycloak.org/docs/latest/authorization\_services](https://www.keycloak.org/docs/latest/authorization_services)
- [https://www.keycloak.org/docs/latest/authorization\_services/#\_resource\_overview](https://www.keycloak.org/docs/latest/authorization_services/#_resource_overview)
- [https://github.com/NikiforovAll/keycloak-authorization-services-dotnet](https://github.com/NikiforovAll/keycloak-authorization-services-dotnet)