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
<PackageReference Include="Antaeus.Keycloak.AspNetCore" Version="2.1.0" />
<PackageVersion Include="Antaeus.Keycloak.AspNetCore" Version="2.1.0" />
<PackageReference Include="Antaeus.Keycloak.AspNetCore" />
paket add Antaeus.Keycloak.AspNetCore --version 2.1.0
#r "nuget: Antaeus.Keycloak.AspNetCore, 2.1.0"
#:package Antaeus.Keycloak.AspNetCore@2.1.0
#addin nuget:?package=Antaeus.Keycloak.AspNetCore&version=2.1.0
#tool nuget:?package=Antaeus.Keycloak.AspNetCore&version=2.1.0
Antaeus.Keycloak.AspNetCore
Complete Keycloak integration for ASP.NET Core with minimal configuration
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 codePOST /api/keycloak/device/token- Poll for tokenPOST /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 rolesHasRealmRole(role)- Check for specific roleHasAnyRealmRole(roles)- Check for any of the rolesHasAllRealmRoles(roles)- Check for all rolesGetPreferredUsername()- Get usernameGetEmail()- Get emailGetSubject()- Get user ID (sub claim)GetFullName()- Get full nameGetClaimValues(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:
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 flowsOFF - For Swagger support: Standard flow
ON, add Swagger redirect URI - Valid redirect URIs configured (if using Swagger)
- Web origins:
*or specific origins
- For Bearer-only: Client authentication
- Protocol mappers configured:
- Realm roles mapper (
realm_access.roles) on frontend client - Audience mapper on frontend client pointing to this API
- Realm roles mapper (
- 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
- Client authentication:
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:
- In Keycloak, go to the frontend client (not the API client)
- Add an "Audience" mapper:
- Mapper Type:
Audience - Included Client Audience:
your-api-client-id - Add to access token:
ON
- Mapper Type:
- 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:
- 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
Check token has roles:
- Copy token from Authorization header
- Decode at https://jwt.io
- Verify
realm_access.rolesarray exists
Ensure frontend client has realm roles mapper
CORS Errors from Frontend
Symptom: Browser blocks API requests with CORS error.
Solution:
- 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()
- In Keycloak client settings:
- Web Origins: Add
http://localhost:5173
- Web Origins: Add
M2M Client Authentication Failed
Symptom: IKeycloakM2MClient.GetAccessTokenAsync() throws exception.
Cause: Incorrect client secret or client not configured for service accounts.
Solution:
- Verify client secret matches between Keycloak and your app
- Check client has "Service accounts roles" enabled
- 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
- Keycloak Documentation - Official docs home
- Getting Started Guide - Install and basics
- Server Administration Guide - Configure realms, clients, users
Security & Authentication
- Securing Applications - Integrate apps with Keycloak
- Server Developer Guide - Custom extensions
- Authorization Services - Fine-grained authorization
Protocols & Standards
- OpenID Connect - OIDC implementation in Keycloak
- OAuth 2.0 - OAuth 2.0 support
- SAML - SAML 2.0 integration
Client Configuration
- Client Registration - Create and configure clients
- Client Scopes - Manage OAuth scopes
- Protocol Mappers - Customize token claims
Advanced Topics
- Service Accounts - M2M authentication
- Device Authorization Grant - OAuth 2.0 device flow
- Token Exchange - Exchange tokens between services
- User Federation - LDAP, Active Directory integration
- Identity Brokering - Social login, external IdPs
REST API
- Admin REST API - Manage Keycloak programmatically
- OpenID Connect Endpoints - Token, auth, userinfo endpoints
Migration & Upgrades
- Upgrading Guide - Version migration
- Release Notes - What's new
Related Documentation
- 📖 CONFIGURATION.md - Complete parameter reference
- 📖 MIGRATION.md - Migrate from other auth libraries
- 📖 PERFORMANCE.md - Optimization guide
- 📖 Keycloak Setup Guide - Complete Keycloak configuration guide
- 📖 antaeus.keycloak.react - Frontend React library
- 📖 Quick Start - Get started quickly
- 📖 Deployment Guide - Production deployment
License
MIT
Support
For issues and feature requests, please visit GitHub Issues.
Additional Resources
- OAuth 2.0 / OpenID Connect Explained
- ASP.NET Core Authentication
- ASP.NET Core Authorization
- JWT.io - Decode and inspect JWT tokens
| Product | Versions 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. |
-
net8.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.0)
- Microsoft.Extensions.Caching.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- Microsoft.Extensions.Http (>= 8.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- Swashbuckle.AspNetCore (>= 6.5.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.