Type at least 2 characters to search...
Using Keycloak in .NET Aspire projects dotnet aspnetcore keycloak
Announcement - Keycloak.AuthServices v2.3.0 is out 🎉! aspnetcore dotnet keycloak auth
Announcement - Keycloak.AuthServices v2.0.0 is out 🎉! aspnetcore dotnet keycloak auth
Keycloak as Authorization Server in .NET aspnetcore dotnet auth keycloak

What’s new

Keycloak.AuthServices just got a round of updates. Keycloak itself has changed a lot between versions 24 and 26 – lightweight access tokens, organizations, RFC 8414 metadata – and the .NET library needed to catch up.

This post walks through the three biggest additions.


Package Version Description
Keycloak.AuthServices.Authentication Nuget Authentication for API and Web Apps
Keycloak.AuthServices.Authorization Nuget Authorization Services + Authorization Server integration
Keycloak.AuthServices.Authorization.TokenIntrospection Nuget Token introspection for lightweight access tokens
Keycloak.AuthServices.Sdk Nuget Admin API and Protection API
Keycloak.AuthServices.Sdk.Kiota Nuget Admin API based on OpenAPI (Kiota-generated)

Organization authorization

Keycloak 24 introduced organizations as a first-class concept – and for multi-tenant applications, this changes things.

Before organizations, multi-tenancy in Keycloak usually meant one realm per tenant (operational headache) or custom attributes and groups hacked together to represent “who belongs where”. Organizations give you a built-in abstraction: users belong to organizations, organizations have their own identity providers, and membership shows up directly in the token.

For a SaaS app, this means you can stop reinventing tenant isolation. Keycloak manages the “which user belongs to which tenant” question, and the token carries that answer. Your API just needs to enforce it.

The library picks that up and gives you authorization policies that work with it.

Keycloak produces two claim formats depending on the Organization Membership Mapper configuration – a simple string array or a rich JSON map with IDs and attributes. Both are handled transparently.

To include organization membership in tokens, request the organization:* scope. The plain organization scope alone does not include the claim.

Require any organization membership

The simplest check – the user must belong to at least one organization:

app.MapGet("/orgs", () => "You belong to an org")
    .RequireOrganizationMembership();

Require a specific organization

app.MapGet("/acme/settings", () => "Acme settings")
    .RequireOrganizationMembership("acme-corp");

Resolve the organization from route or header

Sometimes the organization comes from the request itself. The library ships with built-in resolvers for routes, headers, and query parameters:

app.MapGet("/orgs/{orgId}/projects", (string orgId) => $"Projects for {orgId}")
    .RequireOrganizationMembership("{orgId}");

app.MapGet("/tenant/projects", () => "Tenant projects")
    .RequireOrganizationMembership<RouteHandlerBuilder, HeaderParameterResolver>("{X-Organization}");

Policy-based approach

If you prefer named policies:

services.AddKeycloakAuthorization()
    .AddAuthorizationBuilder()
    .AddPolicy("AcmeOnly", policy =>
        policy.RequireOrganizationMembership("acme-corp")
              .RequireRealmRoles("user"));

Imperative authorization

For more dynamic scenarios:

var result = await authorizationService.AuthorizeAsync(
    user, null, new OrganizationRequirement(orgId));

The OrganizationRequirementHandler reads the organization claim, parses the JSON structure Keycloak emits, and checks membership. You can configure which claim type to look at if your setup uses a non-default claim name.


Token introspection for lightweight access tokens

This one bit me in production. Keycloak 24+ supports lightweight access tokens – valid signed JWTs, but intentionally stripped of business claims like resource_access, realm_access, and preferred_username. The idea is to keep tokens small.

The problem: role-based authorization silently fails. KeycloakRolesClaimsTransformation looks for resource_access, finds nothing, maps no roles. Every role check returns 403. No errors, no warnings. Just 403s.

The fix is token introspection. You send the token back to Keycloak’s introspection endpoint and get the full claim set.

Installation

Token introspection ships as a separate package:

dotnet add package Keycloak.AuthServices.Authorization.TokenIntrospection

Enabling introspection

services.AddKeycloakWebApiAuthentication(configuration);

services.AddKeycloakAuthorization(options =>
{
    options.EnableRolesMapping = RolesClaimTransformationSource.All;
});

services.AddKeycloakTokenIntrospection(configuration);

That’s it. The AddKeycloakTokenIntrospection call registers a claims transformation that:

  1. Checks if expected claims (resource_access, realm_access) are already present
  2. If missing, calls the introspection endpoint with client credentials
  3. Merges the the full claim set into the ClaimsPrincipal
  4. Caches results per token to avoid redundant calls

The existing role mapping transformation runs after introspection, so everything works transparently.

Configuration

Via appsettings.json:

{
  "Keycloak": {
    "realm": "my-realm",
    "auth-server-url": "https://keycloak.example.com/",
    "resource": "my-api",
    "credentials": {
      "secret": "my-client-secret"
    }
  }
}

The introspection client reuses KeycloakInstallationOptions, so if you already have Keycloak configured, you likely don’t need to add anything.

Cache duration defaults to 60 seconds and is configurable:

services.AddKeycloakTokenIntrospection(options =>
{
    configuration.BindKeycloakOptions(options);
    options.CacheDuration = TimeSpan.FromSeconds(120);
});

When to use this

You need this if:

  • Your Keycloak admin enabled lightweight access tokens
  • You’re using KC 26+ admin clients (they use lightweight tokens by default)
  • You see unexplained 403s after upgrading Keycloak

The alternative is configuring Keycloak protocol mappers with the “Add to lightweight access token” flag, which avoids the introspection round-trip entirely. Pick your trade-off.


RFC 8414 metadata discovery

Since Keycloak 26.4, the RFC 8414 metadata endpoint is available at /.well-known/oauth-authorization-server, alongside the traditional OIDC discovery at /.well-known/openid-configuration.

Why does this matter? If you’re building a pure OAuth 2.0 resource server – no ID tokens, no OIDC – then OIDC discovery is technically the wrong endpoint. More practically, Keycloak 26.4 added MCP (Model Context Protocol) authorization server support, which uses RFC 8414 discovery.

Using RFC 8414 metadata

services.AddKeycloakWebApiAuthentication(options =>
{
    configuration.BindKeycloakOptions(options);
    options.MetadataAddress = KeycloakConstants.OAuthAuthorizationServerMetadataPath;
});

The MetadataAddress property overrides the default OIDC discovery path. The constant OAuthAuthorizationServerMetadataPath resolves to ".well-known/oauth-authorization-server".

If you don’t set it, nothing changes – the library defaults to OIDC discovery as before.

When to use this

  • Machine-to-machine flows (client credentials, no ID tokens)
  • OAuth 2.0-only clients that expect RFC 8414 metadata
  • MCP authorization server scenarios with Keycloak 26.4+
  • Protocol correctness for pure resource servers

Also in this release

A few more things that shipped in this release:

  • Extensible policy builderIProtectedResourcePolicyBuilder lets you customize how protected resource policies are constructed
  • Pluggable parameter resolution – custom IParameterResolver implementations for resolving resource names from routes, headers, query strings, or your own sources
  • Configurable organization claim type – if your Keycloak setup uses a non-standard claim name for organization data
  • Kiota SDK updated to Keycloak 26.5.6 OpenAPI spec

Full changelog: GitHub Releases

Documentation: nikiforovall.github.io/keycloak-authorization-services-dotnet


AI skills for Claude Code

This release also ships a Claude Code plugin with two AI skills that help you work with Keycloak directly from your terminal.

keycloak-auth-services

An implementation guide that knows the library’s API surface – authentication setup, authorization patterns, resource protection, Admin SDK, Protection API, and configuration options. Ask it how to set up JWT Bearer auth or configure protected resources and it’ll give you working code using the current API.

keycloak-administration

A Keycloak server administration guide covering realm management, client configuration, authentication flows, RBAC, user federation, security hardening, and troubleshooting. Useful when you need to configure the Keycloak side of things – setting up clients, mappers, organizations, or debugging token issues.


Join us on Discord: Discord


Oleksii Nikiforov

Jibber-jabbering about programming and IT.