LowCodeHub.Keycloak.Authentication.JwtBearer
0.0.11
dotnet add package LowCodeHub.Keycloak.Authentication.JwtBearer --version 0.0.11
NuGet\Install-Package LowCodeHub.Keycloak.Authentication.JwtBearer -Version 0.0.11
<PackageReference Include="LowCodeHub.Keycloak.Authentication.JwtBearer" Version="0.0.11" />
<PackageVersion Include="LowCodeHub.Keycloak.Authentication.JwtBearer" Version="0.0.11" />
<PackageReference Include="LowCodeHub.Keycloak.Authentication.JwtBearer" />
paket add LowCodeHub.Keycloak.Authentication.JwtBearer --version 0.0.11
#r "nuget: LowCodeHub.Keycloak.Authentication.JwtBearer, 0.0.11"
#:package LowCodeHub.Keycloak.Authentication.JwtBearer@0.0.11
#addin nuget:?package=LowCodeHub.Keycloak.Authentication.JwtBearer&version=0.0.11
#tool nuget:?package=LowCodeHub.Keycloak.Authentication.JwtBearer&version=0.0.11
LowCodeHub.Keycloak.Authentication.JwtBearer
Keycloak JWT Bearer authentication for ASP.NET Core. The package wires up AddJwtBearer, validates Keycloak options at startup, configures OpenID Connect metadata and JWKS retrieval, and maps Keycloak realm/client roles into normal ASP.NET Core role claims.
Why This Library?
| Feature | LowCodeHub.Keycloak.Authentication.JwtBearer | Manual JwtBearer setup |
|---|---|---|
| Keycloak URLs | Derived from KeycloakBaseUrl and Realm |
Build authority, metadata, and JWKS URLs manually |
| Startup validation | Required options fail fast | Usually discovered on first request |
| Role mapping | Realm roles and client roles mapped automatically | Custom claims transformation required |
| JWKS retrieval | Metadata and JWKS configured together | Default behavior may need extra setup behind proxies |
| Query token support | Optional ?access_token= support for SSE/WebSocket scenarios |
Custom OnMessageReceived code |
| Failure headers | Optional token failure headers for SPA clients | Custom OnAuthenticationFailed code |
Installation
dotnet add package LowCodeHub.Keycloak.Authentication.JwtBearer
Quick Start
using LowCodeHub.Keycloak.Authentication.JwtBearer.Extensions;
builder.Services.AddKeycloakJwtBearerAuthentication(options =>
{
options.KeycloakBaseUrl = "https://keycloak.example.com";
options.Realm = "my-realm";
options.Audience = "my-api";
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/me", (ClaimsPrincipal user) => new
{
Name = user.Identity?.Name,
Roles = user.FindAll("role").Select(c => c.Value)
}).RequireAuthorization();
app.Run();
Tokens issued by Keycloak are validated against the configured issuer and audience. Realm roles from realm_access.roles and client roles from resource_access.{Audience}.roles are copied to the configured role claim type, so standard ASP.NET Core authorization works:
app.MapGet("/admin", () => "ok")
.RequireAuthorization(policy => policy.RequireRole("admin"));
Configuration
Code-Based Configuration
builder.Services.AddKeycloakJwtBearerAuthentication(options =>
{
options.KeycloakBaseUrl = "https://keycloak.example.com";
options.Realm = "my-realm";
options.Audience = "orders-api";
options.RequireHttpsMetadata = true;
});
appsettings.json
builder.Services.AddKeycloakJwtBearerAuthentication(builder.Configuration);
{
"Keycloak": {
"KeycloakBaseUrl": "https://keycloak.example.com",
"Realm": "my-realm",
"Audience": "orders-api",
"RequireHttpsMetadata": true
}
}
Use a custom section name when needed:
builder.Services.AddKeycloakJwtBearerAuthentication(
builder.Configuration,
sectionName: "Authentication:Keycloak");
Options
| Option | Type | Default | Required | Description |
|---|---|---|---|---|
KeycloakBaseUrl |
string |
- | Yes | Base URL of the Keycloak server, for example https://keycloak.example.com |
Realm |
string |
- | Yes | Keycloak realm name |
Audience |
string |
- | Yes | Expected token audience, usually your API/client ID |
Issuer |
string |
Authority |
No | Expected issuer. Override when Keycloak is behind a reverse proxy with a different external issuer URL |
RequireHttpsMetadata |
bool |
true |
No | Requires HTTPS for metadata and JWKS endpoints |
ClockSkew |
TimeSpan? |
15 seconds |
No | Allowed clock skew for token lifetime validation |
RoleClaimType |
string |
role |
No | Claim type used for mapped roles |
NameClaimType |
string |
name |
No | Claim type used as ClaimsIdentity.NameClaimType |
SaveToken |
bool |
false |
No | Stores the bearer token in authentication properties |
AcceptTokenFromQueryString |
bool |
false |
No | Accepts ?access_token= as a bearer token source |
EmitFailureResponseHeaders |
bool |
false |
No | Emits token failure headers on authentication failures |
JwksOption |
JwksOptions |
new() |
No | JWKS refresh and backchannel settings |
Computed URLs:
| Property | Value |
|---|---|
Authority |
{KeycloakBaseUrl}/realms/{Realm} |
MetadataAddress |
{Authority}/.well-known/openid-configuration |
CertsAddress |
{Authority}/protocol/openid-connect/certs |
Role Mapping
Keycloak stores roles in JSON claims, not as simple repeated role claims:
{
"realm_access": {
"roles": ["admin", "support"]
},
"resource_access": {
"orders-api": {
"roles": ["orders.read", "orders.write"]
}
}
}
The package registers IClaimsTransformation and copies those roles into the configured RoleClaimType (role by default). It is idempotent, so repeated transformations do not duplicate role claims.
builder.Services.AddKeycloakJwtBearerAuthentication(options =>
{
options.KeycloakBaseUrl = "https://keycloak.example.com";
options.Realm = "my-realm";
options.Audience = "orders-api";
options.RoleClaimType = ClaimTypes.Role;
});
Named Authentication Scheme
By default, the package registers the normal Bearer scheme as the default authenticate/challenge scheme.
For apps with multiple authentication schemes, pass a custom scheme name:
builder.Services.AddKeycloakJwtBearerAuthentication(options =>
{
options.KeycloakBaseUrl = "https://keycloak.example.com";
options.Realm = "my-realm";
options.Audience = "orders-api";
}, authenticationScheme: "KeycloakBearer");
app.MapGet("/secure", () => "ok")
.RequireAuthorization(new AuthorizeAttribute
{
AuthenticationSchemes = "KeycloakBearer"
});
JWKS Options
Use JwksOption to tune metadata/JWKS refresh behavior or supply a custom backchannel handler.
builder.Services.AddKeycloakJwtBearerAuthentication(options =>
{
options.KeycloakBaseUrl = "https://keycloak.example.com";
options.Realm = "my-realm";
options.Audience = "orders-api";
options.JwksOption = new JwksOptions
{
AutomaticRefreshInterval = TimeSpan.FromHours(6),
RefreshInterval = TimeSpan.FromMinutes(2)
};
});
For local development with self-signed certificates:
options.JwksOption.BackchannelHttpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
Do not use that certificate bypass in production.
Query String Tokens
Bearer tokens should normally be sent in the Authorization header. Some browser-based transports, such as Server-Sent Events or WebSockets, may need query string tokens.
options.AcceptTokenFromQueryString = true;
When enabled, the package reads ?access_token= during OnMessageReceived.
Failure Headers
For SPA clients that need a simple refresh signal, enable failure headers:
options.EmitFailureResponseHeaders = true;
Authentication failures can emit:
| Header | Meaning |
|---|---|
Token-Expired: true |
Token lifetime validation failed because the token expired |
Token-Validation: false |
Token validation failed |
This can reveal authentication details, so keep it disabled for public APIs unless clients require it.
Common Gotchas
RequireHttpsMetadata now defaults to true
Starting with version 0.0.3, RequireHttpsMetadata defaults to true, matching the ASP.NET Core framework default. This is a behavioral change: plain-HTTP development setups (e.g., http://localhost:8080 Keycloak) must now opt out explicitly:
options.RequireHttpsMetadata = false; // development only
Keycloak audience must match Audience
ASP.NET Core validates the JWT aud claim. Many Keycloak clients do not include your API/client ID in aud by default.
If validation fails with an audience error, add an audience mapper in Keycloak:
- Open your client in Keycloak Admin Console.
- Go to Client scopes or Mappers.
- Add an Audience mapper.
- Include your API/client ID in the access token audience.
azp and aud are different claims. This package validates aud.
Reverse proxies can change the issuer
If Keycloak is reached internally at one URL but tokens contain an external issuer URL, override Issuer:
options.KeycloakBaseUrl = "http://keycloak:8080";
options.Issuer = "https://auth.example.com/realms/my-realm";
Middleware order matters
Use authentication before authorization:
app.UseAuthentication();
app.UseAuthorization();
How It Works
AddKeycloakJwtBearerAuthenticationbinds and validatesKeycloakOptions.AddJwtBeareris configured with authority, metadata address, issuer, audience, lifetime, name claim, and role claim settings.- JWKS retrieval uses the configured certs endpoint and refresh intervals.
KeycloakRolesClaimsTransformationmaps Keycloak realm/client roles into standard role claims.- Optional events handle query string tokens and failure response headers.
Requirements
- .NET 10 or later
- ASP.NET Core
- Keycloak access tokens with a valid issuer and audience
License
MIT
| Product | Versions 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. |
-
net10.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.