SyntaxCircus.AspNetCore.Authentication
0.1.4
dotnet add package SyntaxCircus.AspNetCore.Authentication --version 0.1.4
NuGet\Install-Package SyntaxCircus.AspNetCore.Authentication -Version 0.1.4
<PackageReference Include="SyntaxCircus.AspNetCore.Authentication" Version="0.1.4" />
<PackageVersion Include="SyntaxCircus.AspNetCore.Authentication" Version="0.1.4" />
<PackageReference Include="SyntaxCircus.AspNetCore.Authentication" />
paket add SyntaxCircus.AspNetCore.Authentication --version 0.1.4
#r "nuget: SyntaxCircus.AspNetCore.Authentication, 0.1.4"
#:package SyntaxCircus.AspNetCore.Authentication@0.1.4
#addin nuget:?package=SyntaxCircus.AspNetCore.Authentication&version=0.1.4
#tool nuget:?package=SyntaxCircus.AspNetCore.Authentication&version=0.1.4
SyntaxCircus.AspNetCore.Authentication
JWT bearer authentication setup and pluggable API-key authentication for ASP.NET Core APIs — the server side of validating incoming requests. (For a Blazor Server app forwarding its own user's OIDC tokens to a backend API, see SyntaxCircus.Blazor.Auth instead — that's a different concern.)
No support guaranteed. Published as-is and maintained on a best-effort basis. Issues and PRs are welcome, but there's no SLA — fork it or vendor what you need if that's not enough.
JWT bearer
builder.Services.AddSyntaxCircusJwtBearer(builder.Configuration); // binds "Authentication:JwtBearer"
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
{
"Authentication": {
"JwtBearer": {
"Authority": "https://auth.example.com/application/o/my-web/",
"Audiences": ["my-api"],
"TrustedIssuers": [
"https://auth.example.com/application/o/my-admin/",
"https://auth.example.com/application/o/my-mobile/"
],
"RequireHttpsMetadata": true,
"MapInboundClaims": false,
"RoleClaimType": "roles",
"LogAuthenticationFailuresInDevelopment": true,
"DeviceToken": {
"Enabled": true,
"SigningKey": "a long random symmetric key",
"Issuer": "my-api",
"Audience": "my-api-devices"
}
}
}
}
Registers a primary scheme (JwtBearerDefaults.AuthenticationScheme) that validates tokens against OIDC-discovered signing keys from Authority. When DeviceToken:Enabled is set, a second symmetric-key scheme (SyntaxCircusJwtBearerExtensions.DeviceScheme, "SyntaxCircusDevice") is also registered for self-issued device/M2M tokens, plus a policy scheme (SyntaxCircusJwtBearerExtensions.PolicyScheme, "SyntaxCircusJwt", and the app's default scheme in that case) that picks between the two automatically per request by sniffing the incoming JWT's alg header (HS256 → device scheme, anything else → the primary OIDC scheme) — callers don't need to know or specify which scheme applies. [Authorize] with no explicit scheme uses the policy scheme automatically; target DeviceScheme explicitly ([Authorize(AuthenticationSchemes = SyntaxCircusJwtBearerExtensions.DeviceScheme)]) if you need to require a device token specifically.
TrustedIssuers accepts additional issuers beyond Authority. Entries that are genuinely distinct OIDC applications — e.g. separate Web/Admin/Mobile registrations against the same identity provider, each with its own signing keys — get their own OIDC discovery (/.well-known/openid-configuration + JWKS) so their tokens validate too, not just tokens whose issuer claim happens to differ while sharing Authority's keys. There's no extra discovery round-trip when TrustedIssuers is empty or only repeats Authority.
A few more optional settings, all of which leave ASP.NET Core's own default untouched when unset:
RequireHttpsMetadata(bool) — whether OIDC discovery endpoints must be served over HTTPS.MapInboundClaims(bool) — set tofalseto preserve raw claim types (e.g. a custom"roles"claim) instead of ASP.NET Core's defaultClaimTypes.*remapping.RoleClaimType/NameClaimType(string) — override which claim type role-based authorization (IsInRole) andIdentity.Nameread from, for tokens using non-default claim names.LogAuthenticationFailuresInDevelopment(bool, defaultfalse) — opt-in dev-only logging of the validation exception on authentication failure, viaOnAuthenticationFailed.
Composite opaque and JWT bearer credentials
Some APIs accept both database-backed opaque bearer credentials and OIDC JWTs through the same
Authorization: Bearer header. Register the opaque handler in the application, register this
package's JWT handler, then use AddSyntaxCircusCompositeBearer to route readable JWTs to the JWT
scheme and every other bearer credential to the opaque handler:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "CompositeBearer";
options.DefaultChallengeScheme = "CompositeBearer";
})
.AddScheme<MyOpaqueBearerOptions, MyOpaqueBearerHandler>("OpaqueBearer", _ => { })
.AddSyntaxCircusCompositeBearer("OpaqueBearer", JwtBearerDefaults.AuthenticationScheme, "CompositeBearer");
builder.Services.AddSyntaxCircusJwtBearer(builder.Configuration);
The composite selector only chooses an authentication scheme; it neither validates opaque tokens nor maps application claims. Keep database lookup and application-specific authorization mapping in the consuming API.
API key
builder.Services.AddSyntaxCircusApiKey(builder.Configuration); // binds "Authentication:ApiKey", scheme "ApiKey"
{ "Authentication": { "ApiKey": { "HeaderName": "X-Api-Key", "StaticKey": "..." } } }
By default, validates the X-Api-Key header against a single configured StaticKey using a constant-time comparison. For per-caller or DB-backed/hashed-key validation, register your own IApiKeyValidator before calling AddSyntaxCircusApiKey:
builder.Services.AddSingleton<IApiKeyValidator, MyDatabaseBackedApiKeyValidator>();
builder.Services.AddSyntaxCircusApiKey(builder.Configuration);
Your validator returns ApiKeyValidationResult.Valid(claims) or ApiKeyValidationResult.Invalid — whatever claims you supply become the resulting ClaimsPrincipal, so you can carry caller identity, scopes, or partition keys through to your endpoints.
Multiple independent API-key schemes
AddSyntaxCircusApiKey accepts an optional schemeName (defaulting to ApiKeyAuthenticationHandler.SchemeName, i.e. "ApiKey"), alongside the existing sectionName. Call it more than once with distinct pairs to register genuinely independent API-key concerns in the same app — e.g. a worker-to-worker key and an external-agent key, each with its own header name and its own validator:
builder.Services.AddKeyedSingleton<IApiKeyValidator, WorkerApiKeyValidator>("WorkerApiKey");
builder.Services.AddSyntaxCircusApiKey(builder.Configuration, "Authentication:WorkerApiKey", "WorkerApiKey");
builder.Services.AddKeyedSingleton<IApiKeyValidator, AgentApiKeyValidator>("AgentApiKey");
builder.Services.AddSyntaxCircusApiKey(builder.Configuration, "Authentication:AgentApiKey", "AgentApiKey");
{
"Authentication": {
"WorkerApiKey": { "HeaderName": "X-Worker-Api-Key" },
"AgentApiKey": { "HeaderName": "X-Agent-Api-Key" }
}
}
Register each scheme's validator keyed by its scheme name (AddKeyedSingleton<IApiKeyValidator, T>(schemeName)) before calling AddSyntaxCircusApiKey for that scheme — the handler looks up a keyed validator for its own scheme first, falling back to the plain (unkeyed) IApiKeyValidator registration only when no keyed match exists. This means a single-scheme app needs no changes at all: with only one scheme registered, there's nothing keyed to find, so it falls straight through to the unkeyed validator exactly as before.
Wire each scheme into authorization separately, e.g.:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Worker", p => p.AddAuthenticationSchemes("WorkerApiKey").RequireAuthenticatedUser());
options.AddPolicy("Agent", p => p.AddAuthenticationSchemes("AgentApiKey").RequireAuthenticatedUser());
});
Caveat: the built-in ConstantApiKeyValidator (used when you don't register your own IApiKeyValidator) is only ever wired up unkeyed, for exactly one scheme — the first AddSyntaxCircusApiKey call in your app. Every additional scheme must supply its own validator (custom, or your own keyed ConstantApiKeyValidator-style implementation reading IOptionsMonitor<ApiKeyAuthenticationOptions>.Get(schemeName)). Relatedly, if a custom validator injects unnamed IOptions<ApiKeyAuthenticationOptions> to read shared config, remember that in a multi-scheme app that unnamed instance reflects whichever scheme's sectionName was registered last — inject IOptionsMonitor<ApiKeyAuthenticationOptions> and call .Get(schemeName) instead so you read the config for the right scheme.
Contributing
Issues and pull requests are welcome:
- Keep changes focused, with a clear description of the behavior change.
- Match the existing code style (see
.editorconfig). - Call out any breaking changes to the public API in your PR description.
License
MIT — see LICENSE.txt.
| 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.11)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 8.19.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.