Antaeus.Keycloak.AspNetCore 2.1.0

dotnet add package Antaeus.Keycloak.AspNetCore --version 2.1.0
                    
NuGet\Install-Package Antaeus.Keycloak.AspNetCore -Version 2.1.0
                    
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="Antaeus.Keycloak.AspNetCore" Version="2.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Antaeus.Keycloak.AspNetCore" Version="2.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Antaeus.Keycloak.AspNetCore" />
                    
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 Antaeus.Keycloak.AspNetCore --version 2.1.0
                    
#r "nuget: Antaeus.Keycloak.AspNetCore, 2.1.0"
                    
#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 Antaeus.Keycloak.AspNetCore@2.1.0
                    
#: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=Antaeus.Keycloak.AspNetCore&version=2.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Antaeus.Keycloak.AspNetCore&version=2.1.0
                    
Install as a Cake Tool

Antaeus.Keycloak.AspNetCore

Complete Keycloak integration for ASP.NET Core with minimal configuration

NuGet License: MIT

Features

Authentication & Authorization

  • JWT Bearer Authentication - Automatic token validation
  • Role-Based Authorization - Built-in policy helpers
  • M2M Client Credentials - Service-to-service authentication with token caching
  • Device Flow Support - Backend proxy for TV/IoT device login

Developer Experience

  • Minimal Configuration - Works with sensible defaults
  • Swagger Integration - OAuth2 authentication in Swagger UI
  • CORS Support - Pre-configured for frontend apps
  • Full XML Documentation - IntelliSense everywhere
  • Strongly Typed Options - Configuration validation

Installation

dotnet add package Antaeus.Keycloak.AspNetCore

Quick Start

1. Configure appsettings.json

{
  "Keycloak": {
    "Authority": "https://sso.example.com/realms/myrealm",
    "Audience": "my-api",
    "RequireHttpsMetadata": true
  }
}

2. Add Keycloak authentication to Program.cs

using Antaeus.Keycloak.AspNetCore.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Add Keycloak authentication
builder.Services.AddKeycloakAuthentication(builder.Configuration);

// Add authorization (optional)
builder.Services.AddKeycloakAuthorization(options =>
{
    options.AddKeycloakRolePolicy("AdminOnly", "admin");
    options.AddKeycloakRolePolicy("UserOrAdmin", "user", "admin");
});

var app = builder.Build();

// Enable authentication & authorization
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

3. Protect your endpoints

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    // Requires any authenticated user
    [Authorize]
    [HttpGet]
    public IActionResult GetUsers()
    {
        return Ok(new[] { "user1", "user2" });
    }

    // Requires specific role
    [Authorize(Roles = "admin")]
    [HttpDelete("{id}")]
    public IActionResult DeleteUser(string id)
    {
        return Ok();
    }

    // Requires custom policy
    [Authorize(Policy = "AdminOnly")]
    [HttpPost]
    public IActionResult CreateUser([FromBody] User user)
    {
        return Ok();
    }
}

Configuration

appsettings.json Structure

{
  "Keycloak": {
    "Authority": "https://sso.example.com/realms/myrealm",
    "Audience": "my-api",
    "RequireHttpsMetadata": true,
    "ClockSkewMinutes": 2,
    "RoleClaimType": "realm_access.roles",
    "SwaggerClientId": "my-api",
    "EnableDebugLogging": false,

    "M2M": {
      "TokenEndpoint": "https://sso.example.com/realms/myrealm/protocol/openid-connect/token",
      "ClientId": "my-service",
      "ClientSecret": "your-client-secret",
      "EnableCaching": true,
      "CacheRefreshThresholdSeconds": 60
    },

    "DeviceFlow": {
      "ClientId": "my-device-client",
      "Scope": "openid profile email roles",
      "EnableEndpoints": true,
      "EnableDebugLogging": false
    },

    "Cors": {
      "AllowedOrigins": [
        "http://localhost:3000",
        "https://app.example.com"
      ],
      "AllowCredentials": true
    }
  }
}

Features in Detail

1. JWT Bearer Authentication

Automatic JWT token validation with Keycloak:

// Basic setup (from configuration)
builder.Services.AddKeycloakAuthentication(builder.Configuration);

// OR configure programmatically
builder.Services.AddKeycloakAuthentication(options =>
{
    options.Authority = "https://sso.example.com/realms/myrealm";
    options.Audience = "my-api";
    options.RequireHttpsMetadata = true;
    options.EnableDebugLogging = true;
});

2. Authorization Policies

Create role-based and custom policies:

builder.Services.AddKeycloakAuthorization(options =>
{
    // Role-based policy (requires at least one role)
    options.AddKeycloakRolePolicy("Editor", "editor", "admin");

    // All roles required
    options.AddKeycloakAllRolesPolicy("PremiumUser", "verified", "premium");

    // Custom claim-based policy
    options.AddKeycloakClaimPolicy("CanAccessReports",
        "realm_access.roles",
        "admin", "reports-viewer");

    // Custom assertion
    options.AddPolicy("CustomPolicy", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c => c.Type == "email_verified" && c.Value == "true")));
});

3. M2M Client Credentials Flow

For service-to-service authentication:

Configuration:

{
  "Keycloak": {
    "M2M": {
      "TokenEndpoint": "https://sso.example.com/realms/myrealm/protocol/openid-connect/token",
      "ClientId": "my-service",
      "ClientSecret": "your-secret",
      "EnableCaching": true
    }
  }
}

Setup:

builder.Services.AddKeycloakM2MClient(builder.Configuration);

Usage:

public class MyService
{
    private readonly IKeycloakM2MClient _m2mClient;
    private readonly HttpClient _httpClient;

    public MyService(IKeycloakM2MClient m2mClient, HttpClient httpClient)
    {
        _m2mClient = m2mClient;
        _httpClient = httpClient;
    }

    public async Task CallOtherServiceAsync()
    {
        // Get access token (automatically cached)
        var token = await _m2mClient.GetAccessTokenAsync();

        // Use token in API calls
        _httpClient.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        var response = await _httpClient.GetAsync("https://other-service.com/api/data");
        // ...
    }
}

4. Device Flow for TV/IoT Devices

Backend proxy endpoints for device authorization flow:

Configuration:

{
  "Keycloak": {
    "DeviceFlow": {
      "ClientId": "my-tv-app",
      "Scope": "openid profile email roles",
      "EnableEndpoints": true
    }
  }
}

Setup:

builder.Services.AddKeycloakDeviceFlow(builder.Configuration);

This automatically exposes three endpoints:

  • POST /api/keycloak/device/authorize - Request device code
  • POST /api/keycloak/device/token - Poll for token
  • POST /api/keycloak/device/userinfo - Get user info

These endpoints are used by the React DeviceLogin component.

5. Swagger Integration

Add OAuth2 authentication to Swagger UI:

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "My API",
        Version = "v1"
    });

    // Add Keycloak OAuth2
    options.AddKeycloakOAuth2(builder.Configuration);
});

// Configure Swagger UI
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
    options.OAuthClientId(builder.Configuration["Keycloak:SwaggerClientId"]);
    options.OAuthUsePkce();
});

6. CORS Support

Pre-configured CORS for frontend apps:

// Add CORS
builder.Services.AddKeycloakCors(builder.Configuration);

// Use CORS
app.UseKeycloakCors();

7. ClaimsPrincipal Extensions

Convenient extension methods for working with Keycloak claims:

using Antaeus.Keycloak.AspNetCore.Extensions;

[Authorize]
[HttpGet("profile")]
public IActionResult GetProfile()
{
    var user = User; // ClaimsPrincipal

    return Ok(new
    {
        Id = user.GetSubject(),
        Username = user.GetPreferredUsername(),
        Email = user.GetEmail(),
        FullName = user.GetFullName(),
        Roles = user.GetRealmRoles(),
        IsAdmin = user.HasRealmRole("admin"),
        CanEdit = user.HasAnyRealmRole(new[] { "editor", "admin" })
    });
}

Available extension methods:

  • GetRealmRoles() - Get all realm roles
  • HasRealmRole(role) - Check for specific role
  • HasAnyRealmRole(roles) - Check for any of the roles
  • HasAllRealmRoles(roles) - Check for all roles
  • GetPreferredUsername() - Get username
  • GetEmail() - Get email
  • GetSubject() - Get user ID (sub claim)
  • GetFullName() - Get full name
  • GetClaimValues(type) - Get all values for a claim type

Complete Example

using Antaeus.Keycloak.AspNetCore.Extensions;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Add Keycloak authentication
builder.Services.AddKeycloakAuthentication(builder.Configuration);

// Add authorization policies
builder.Services.AddKeycloakAuthorization(options =>
{
    options.AddKeycloakRolePolicy("AdminOnly", "admin");
    options.AddKeycloakRolePolicy("EditorOrAdmin", "editor", "admin");
});

// Add M2M client
builder.Services.AddKeycloakM2MClient(builder.Configuration);

// Add device flow
builder.Services.AddKeycloakDeviceFlow(builder.Configuration);

// Add CORS
builder.Services.AddKeycloakCors(builder.Configuration);

// Add Swagger with Keycloak OAuth2
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
    options.AddKeycloakOAuth2(builder.Configuration);
});

builder.Services.AddControllers();

var app = builder.Build();

// Middleware pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
        options.OAuthClientId(builder.Configuration["Keycloak:SwaggerClientId"]);
        options.OAuthUsePkce();
    });
}

app.UseKeycloakCors();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

User Secrets for Development

Store sensitive values like client secrets using user secrets:

cd YourProject
dotnet user-secrets set "Keycloak:M2M:ClientSecret" "your-actual-secret"

Keycloak Server Configuration

This library requires proper Keycloak server configuration. See the comprehensive guide:

📖 Keycloak Setup Guide

Quick Keycloak Checklist

Before using this library, ensure your Keycloak server has:

  • Realm created with appropriate settings
  • Confidential or Bearer-only client created for your API with:
    • For Bearer-only: Client authentication ON, all flows OFF
    • For Swagger support: Standard flow ON, add Swagger redirect URI
    • Valid redirect URIs configured (if using Swagger)
    • Web origins: * or specific origins
  • Protocol mappers configured:
    • Realm roles mapper (realm_access.roles) on frontend client
    • Audience mapper on frontend client pointing to this API
  • Roles created and assigned to users
  • M2M client created (if using service-to-service auth):
    • Client authentication: ON
    • Service accounts roles: ON
    • Client secret generated and stored securely

See the full guide for detailed step-by-step instructions.


Requirements

  • .NET 8.0+
  • ASP.NET Core 8.0+
  • System.Net.Http.Json package (included in .NET 8)

API Reference

Extension Methods

AddKeycloakAuthentication(IConfiguration)

Adds JWT Bearer authentication using configuration from appsettings.json

AddKeycloakAuthentication(Action<KeycloakOptions>)

Adds JWT Bearer authentication with programmatic configuration

AddKeycloakAuthorization(Action<AuthorizationOptions>?)

Adds authorization with optional custom policies

AddKeycloakM2MClient(IConfiguration | Action<KeycloakM2MOptions>)

Adds M2M client credentials service

AddKeycloakDeviceFlow(IConfiguration | Action<KeycloakDeviceFlowOptions>)

Adds device flow proxy endpoints

AddKeycloakCors(IConfiguration | string[], KeycloakCorsOptions?)

Adds CORS policy for frontend origins

AddKeycloakOAuth2(IConfiguration | string, string, Dictionary?)

Adds Keycloak OAuth2 to Swagger

Interfaces

IKeycloakM2MClient
Task<string> GetAccessTokenAsync(CancellationToken cancellationToken = default);
Task<string> RefreshAccessTokenAsync(CancellationToken cancellationToken = default);

Troubleshooting

401 Unauthorized - Invalid Audience

Symptom: API rejects tokens with "The audience is invalid"

Cause: Token doesn't have this API's client ID in the aud claim.

Solution:

  1. In Keycloak, go to the frontend client (not the API client)
  2. Add an "Audience" mapper:
    • Mapper Type: Audience
    • Included Client Audience: your-api-client-id
    • Add to access token: ON
  3. Re-login from frontend to get new token

Verify:

// Enable debug logging to see token validation
"Keycloak": {
  "EnableDebugLogging": true
}

// Check logs for exact error message

403 Forbidden - Roles Not Working

Symptom: User has role in Keycloak but [Authorize(Roles = "admin")] returns 403.

Cause: Claims transformation not extracting roles from realm_access JSON.

Solution:

The library includes KeycloakClaimsTransformation which is automatically registered. Verify:

  1. Check if transformation is running:
[Authorize]
[HttpGet("debug/claims")]
public IActionResult GetClaims()
{
    var claims = User.Claims.Select(c => new { c.Type, c.Value });
    return Ok(claims);
}

// Look for claims with type: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
// If not present, transformation didn't run
  1. Check token has roles:

    • Copy token from Authorization header
    • Decode at https://jwt.io
    • Verify realm_access.roles array exists
  2. Ensure frontend client has realm roles mapper

CORS Errors from Frontend

Symptom: Browser blocks API requests with CORS error.

Solution:

  1. In Program.cs, add CORS:
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowFrontend", policy =>
    {
        policy.WithOrigins("http://localhost:5173", "https://app.example.com")
              .AllowAnyMethod()
              .AllowAnyHeader()
              .AllowCredentials();
    });
});

app.UseCors("AllowFrontend");  // Before UseAuthentication()
  1. In Keycloak client settings:
    • Web Origins: Add http://localhost:5173

M2M Client Authentication Failed

Symptom: IKeycloakM2MClient.GetAccessTokenAsync() throws exception.

Cause: Incorrect client secret or client not configured for service accounts.

Solution:

  1. Verify client secret matches between Keycloak and your app
  2. Check client has "Service accounts roles" enabled
  3. Verify token endpoint URL is correct:
    {
      "Keycloak": {
        "M2M": {
          "TokenEndpoint": "https://keycloak.com/realms/myrealm/protocol/openid-connect/token",
          "ClientId": "my-service",
          "ClientSecret": "verify-this-matches-keycloak"
        }
      }
    }
    

Device Flow 500 Error

Symptom: /api/keycloak/device/authorize returns 500 Internal Server Error.

Cause 1: IHttpClientFactory not registered.

Solution:

builder.Services.AddHttpClient();  // Add this!

Cause 2: KeycloakDeviceFlowOptions not configured.

Solution:

builder.Services.Configure<KeycloakDeviceFlowOptions>(options =>
{
    options.ClientId = "my-device-client";
    options.Scope = "openid profile email roles";
});

Cause 3: Device flow not enabled on Keycloak client.

Solution: In Keycloak client capabilities, enable "OAuth 2.0 Device Authorization Grant"

For more troubleshooting, see Keycloak Setup Guide - Troubleshooting.


Security Best Practices

1. Always Require HTTPS in Production

{
  "Keycloak": {
    "RequireHttpsMetadata": true  // Never disable in production!
  }
}

2. Use User Secrets for Development

cd YourProject
dotnet user-secrets init
dotnet user-secrets set "Keycloak:M2M:ClientSecret" "your-secret-here"

Never commit secrets to source control!

3. Use Key Vault in Production

// Azure Key Vault example
builder.Configuration.AddAzureKeyVault(
    new Uri("https://your-vault.vault.azure.net/"),
    new DefaultAzureCredential());

// Secrets in Key Vault:
// Keycloak--M2M--ClientSecret

4. Validate All Token Claims

Don't trust frontend validation alone:

[Authorize(Roles = "admin")]  // ← Backend validation required!
[HttpDelete("/api/users/{id}")]
public IActionResult DeleteUser(string id)
{
    // Even though [Authorize] checks, verify again for critical operations
    if (!User.HasRealmRole("admin"))
    {
        return Forbid();
    }

    // Proceed with deletion
}

5. Use Short Token Lifespans

Configure in Keycloak:

  • Access Token Lifespan: 5 minutes (short)
  • Refresh Token: 30 days (longer, but revocable)

6. Implement Rate Limiting

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
    });
});

[EnableRateLimiting("api")]
[Authorize]
[HttpGet("/api/data")]
public IActionResult GetData() { }

7. Log Authentication Events

builder.Services.AddKeycloakAuthentication(options =>
{
    options.Authority = "https://keycloak.com/realms/myrealm";
    options.Audience = "my-api";
    options.EnableDebugLogging = true;  // Log auth events
});

// Or implement custom logging:
jwtOptions.Events = new JwtBearerEvents
{
    OnAuthenticationFailed = context =>
    {
        _logger.LogWarning("Auth failed: {Error}", context.Exception.Message);
        return Task.CompletedTask;
    },
};

8. Implement API Versioning

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
});

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/users")]
public class UsersController : ControllerBase { }

For complete security guidelines, see Keycloak Setup Guide - Security.


Architecture

How It Works

┌─────────────────┐         ┌──────────────────┐         ┌─────────────────┐
│  React Frontend │◄────────┤   Your ASP.NET   │◄────────┤    Keycloak     │
│  (with token)   │  HTTPS  │   Core API       │  OIDC   │   Auth Server   │
└─────────────────┘         └──────────────────┘         └─────────────────┘
         │                           │
         │  1. User logs in          │  2. API validates
         │     via Keycloak          │     JWT token
         │                           │
         │  3. Receives JWT          │  4. Extracts user
         │     access token          │     claims & roles
         │                           │
         │  4. API calls with        │  5. Authorizes
         │     Bearer token          │     based on roles
         └───────────────────────────┘

Authentication Flow

User Request → [Middleware Pipeline] → Your Controller
                      │
                      ├─ 1. Extract JWT from Authorization header
                      ├─ 2. Validate signature (JWKS from Keycloak)
                      ├─ 3. Validate claims (iss, aud, exp)
                      ├─ 4. Transform claims (roles from realm_access)
                      └─ 5. Populate User.Identity
                            │
                            ├─ IsAuthenticated = true
                            ├─ Name = preferred_username
                            └─ Claims = [roles, email, ...]

M2M Flow (Service-to-Service)

Your Service A                 Keycloak                  Your Service B
      │                            │                            │
      │  1. GetAccessTokenAsync()  │                            │
      ├───────────────────────────►│                            │
      │  (client_id + secret)      │                            │
      │                            │                            │
      │  2. Access Token (JWT)     │                            │
      │◄───────────────────────────┤                            │
      │  (cached for 15 min)       │                            │
      │                            │                            │
      │  3. API Call + Bearer      │                            │
      ├────────────────────────────┼───────────────────────────►│
      │                            │                            │
      │                            │  4. Validates Token        │
      │                            │◄───────────────────────────┤
      │                            │                            │
      │  5. Response               │                            │
      │◄───────────────────────────┼────────────────────────────┤

Official Keycloak Documentation

Quick links to relevant Keycloak documentation for deeper understanding.

Getting Started

Security & Authentication

Protocols & Standards

Client Configuration

Advanced Topics

REST API

Migration & Upgrades


License

MIT

Support

For issues and feature requests, please visit GitHub Issues.

Additional Resources

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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
2.1.0 263 11/7/2025
2.0.2 234 11/3/2025
2.0.1 207 11/2/2025
2.0.0 208 11/2/2025
1.0.7 172 11/2/2025
1.0.6 169 11/2/2025
1.0.5 163 11/2/2025