Dosaic.Plugins.Authorization.Keycloak 1.2.8

dotnet add package Dosaic.Plugins.Authorization.Keycloak --version 1.2.8
                    
NuGet\Install-Package Dosaic.Plugins.Authorization.Keycloak -Version 1.2.8
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Dosaic.Plugins.Authorization.Keycloak" Version="1.2.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Dosaic.Plugins.Authorization.Keycloak" Version="1.2.8" />
                    
Directory.Packages.props
<PackageReference Include="Dosaic.Plugins.Authorization.Keycloak" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Dosaic.Plugins.Authorization.Keycloak --version 1.2.8
                    
#r "nuget: Dosaic.Plugins.Authorization.Keycloak, 1.2.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Dosaic.Plugins.Authorization.Keycloak@1.2.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Dosaic.Plugins.Authorization.Keycloak&version=1.2.8
                    
Install as a Cake Addin
#tool nuget:?package=Dosaic.Plugins.Authorization.Keycloak&version=1.2.8
                    
Install as a Cake Tool

Dosaic.Plugins.Authorization.Keycloak

Dosaic.Plugins.Authorization.Keycloak is a plugin that provides JWT-based authentication and role-driven authorization against a Keycloak server. It implements a custom ASP.NET Core authentication scheme that validates Bearer tokens using Keycloak's realm public key, maps realm and client roles to ClaimTypes.Role, and exposes named authorization policies that can be applied to minimal API endpoints or MVC controllers.

Features

  • Custom keycloak authentication scheme — validates JWT Bearer tokens without a full OIDC discovery round-trip; fetches the realm public key once and caches it in memory
  • Realm & resource-access role mapping — automatically extracts roles from the Keycloak realm_access and resource_access JWT claims and adds them as ClaimTypes.Role claims
  • Named authorization policies — define policies by name with required roles in configuration; policies are registered with ASP.NET Core's IAuthorizationService automatically
  • Disable mode — set Enabled: false to run without any authentication (an allow-all default policy is registered), useful for local development
  • Health check integration — registers a URL health check against the Keycloak management endpoint (defaults to port 9000, path /health/ready) tagged as readiness
  • OpenTelemetry instrumentation — emits authentication_keycloak_success and authentication_keycloak_noresult counters plus distributed tracing spans per authentication attempt
  • Separate health-check host — the health check target can differ from the authentication authority (e.g. internal management port vs. public HTTPS endpoint)

Installation

dotnet add package Dosaic.Plugins.Authorization.Keycloak

Or add the package reference directly to your .csproj:

<PackageReference Include="Dosaic.Plugins.Authorization.Keycloak" Version="" />

Configuration

The plugin is configured under the keycloak key via the [Configuration("keycloak")] attribute on KeycloakPluginConfiguration.

Configuration classes

[Configuration("keycloak")]
public class KeycloakPluginConfiguration
{
    // Toggle the entire plugin. When false, an allow-all authorization policy is registered
    // and no authentication middleware is added.
    public bool Enabled { get; set; }

    // Hostname of the Keycloak authentication server (required when Enabled = true)
    public string Host { get; set; }

    // Optional port for the authority URI (omit to use the default HTTPS/HTTP port)
    public int? Port { get; set; }

    // When true, uses http:// instead of https:// for the authority URI
    public bool Insecure { get; set; }

    // OAuth2 client ID (used to resolve resource_access roles)
    public string ClientId { get; set; }

    // OAuth2 client secret
    public string ClientSecret { get; set; }

    // Realm URL configuration
    public RealmsConfig Realms { get; set; } = new RealmsConfig();

    // Named authorization policies available in the application
    public IList<AuthPolicy> Policies { get; set; } = new List<AuthPolicy>();

    // Health check endpoint configuration (defaults to port 9000, path /health/ready)
    public HealthCheckConfig HealthCheck { get; set; } = new HealthCheckConfig();
}

public class RealmsConfig
{
    // URL path prefix used to build the realm public-key endpoint
    public string Prefix { get; set; } = "/auth/realms";

    // Default realm name
    public string Default { get; set; } = "master";
}

public class HealthCheckConfig
{
    // When true, uses http:// for the health check URL (default: true)
    public bool Insecure { get; set; } = true;

    // Hostname for the Keycloak management/health endpoint
    public string Host { get; set; }

    // Port for the management endpoint (default: 9000)
    public int? Port { get; set; } = 9000;

    // Path for the health check (default: /health/ready)
    public string Prefix { get; set; } = "/health/ready";
}

appsettings.yml example

keycloak:
  enabled: true
  host: keycloak.example.com   # public Keycloak hostname
  port: 443                    # optional; omit to use default HTTPS port
  insecure: false              # false = https (recommended for production)
  clientId: my-service
  clientSecret: supersecret
  realms:
    prefix: /auth/realms
    default: master
  policies:
    - name: READ
      roles:
        - API_PERMISSIONS_READ
    - name: WRITE
      roles:
        - API_PERMISSIONS_WRITE
  healthCheck:
    host: keycloak-internal.example.com  # internal management host
    port: 9000
    insecure: true                       # management port typically not TLS
    prefix: /health/ready

Disable for local development

keycloak:
  enabled: false

When enabled is false, no authentication is required and all requests are allowed through. A warning is logged at startup.

Usage

Minimal API endpoints

public class MyEndpoints : IPluginEndpointsConfiguration
{
    public void ConfigureEndpoints(IEndpointRouteBuilder endpointRouteBuilder, IServiceProvider serviceProvider)
    {
        // Require named policy per verb
        endpointRouteBuilder
            .AddSimpleRestResource<MyResource>(serviceProvider, "my-resource")
            .ForGet(cfg => cfg.WithPolicies("READ"))
            .ForGetList(cfg => cfg.WithPolicies("READ"))
            .ForPost(cfg => cfg.WithPolicies("WRITE"))
            .ForPut(cfg => cfg.WithPolicies("WRITE"))
            .ForDelete(cfg => cfg.WithPolicies("WRITE"))
            .ForAll(cfg => cfg.WithPolicies("WRITE", "READ")); // apply to all verbs at once

        // Or use standard ASP.NET Core policy requirement directly
        endpointRouteBuilder.MapGet("/hello", () => "Hello World!").RequireAuthorization("READ");
    }
}

MVC controllers

[ApiController]
[Route("api/[controller]")]
public class ItemsController : ControllerBase
{
    [HttpGet]
    [Authorize("READ")]
    public IActionResult GetAll() => Ok();

    [HttpPost]
    [Authorize("WRITE")]
    public IActionResult Create([FromBody] Item item) => Ok();

    [HttpGet("public")]
    [AllowAnonymous]
    public IActionResult Public() => Ok("no auth needed");
}

How token validation works

  1. The Authorization: Bearer <token> header is extracted from the incoming request.
  2. The JWT is decoded to read the iss (issuer) claim, which contains the Keycloak realm URL.
  3. PublicKeyService fetches the realm's public key from <authority><realms.prefix>/<realm> and caches it in memory for the lifetime of the process.
  4. The token is validated using JwtSecurityTokenHandler with ValidateIssuerSigningKey = true, ValidateLifetime = true, and ValidateAudience = false.
  5. Roles are extracted from the realm_access.roles array and — when a client ID is present (azp claim) — also from resource_access.<clientId>.roles.
  6. All extracted roles are added as ClaimTypes.Role claims, making them available to [Authorize(Roles = "...")] and RequireRole policies.

Authorization policies

Policies are registered from KeycloakPluginConfiguration.Policies and require the user to have all listed roles:

// Registered automatically — no manual code required:
// options.AddPolicy("READ",  builder => builder.RequireRole("API_PERMISSIONS_READ"));
// options.AddPolicy("WRITE", builder => builder.RequireRole("API_PERMISSIONS_WRITE"));

The default policy requires an authenticated user (i.e., a valid Keycloak token). Unauthenticated requests to unprotected endpoints are still allowed.

Health check

The plugin registers a URL health check named keycloak tagged as readiness:

GET http(s)://<healthCheck.host>:<healthCheck.port><healthCheck.prefix>

With the defaults above this resolves to http://keycloak-internal.example.com:9000/health/ready. The health check returns Unhealthy if the endpoint does not respond with a success status code.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.8 78 3/9/2026
1.2.7 85 3/4/2026
1.2.6 108 2/19/2026
1.2.5 87 2/17/2026
1.2.4 113 2/13/2026
1.2.3 102 1/27/2026
1.2.2 287 12/16/2025
1.2.1 278 12/16/2025
1.2.0 432 12/11/2025
1.1.21 445 12/10/2025
1.1.20 420 11/18/2025
1.1.19 315 11/11/2025
1.1.18 213 10/14/2025
1.1.17 204 10/1/2025
1.1.16 220 9/25/2025
1.1.15 205 9/24/2025
1.1.14 213 9/24/2025
1.1.13 217 9/24/2025
1.1.12 345 9/16/2025
1.1.11 195 7/18/2025
Loading failed