Cirreum.Runtime.Authorization
1.0.27
See the version list below for details.
dotnet add package Cirreum.Runtime.Authorization --version 1.0.27
NuGet\Install-Package Cirreum.Runtime.Authorization -Version 1.0.27
<PackageReference Include="Cirreum.Runtime.Authorization" Version="1.0.27" />
<PackageVersion Include="Cirreum.Runtime.Authorization" Version="1.0.27" />
<PackageReference Include="Cirreum.Runtime.Authorization" />
paket add Cirreum.Runtime.Authorization --version 1.0.27
#r "nuget: Cirreum.Runtime.Authorization, 1.0.27"
#:package Cirreum.Runtime.Authorization@1.0.27
#addin nuget:?package=Cirreum.Runtime.Authorization&version=1.0.27
#tool nuget:?package=Cirreum.Runtime.Authorization&version=1.0.27
Cirreum.Runtime.Authorization
Unified authentication and authorization for ASP.NET Core applications
Overview
Cirreum.Runtime.Authorization is the composition layer that unifies all Cirreum authorization providers into a single, coherent system. It provides dynamic scheme selection, conflict detection, and predefined role-based policies.
Key Features
- Dynamic scheme selection - Automatically routes to the correct authentication handler based on request characteristics
- Fail-closed design - Rejects requests that don't match any configured provider
- Conflict detection - Rejects ambiguous requests with multiple authentication indicators
- Multi-provider support - Entra, API Key, Signed Request, and External (BYOID)
- Cross-scheme policies - Role-based authorization that works across all authentication types
- Predefined policies - Hierarchical role-based policies for common scenarios
Supported Providers
| Provider | Package | Use Case |
|---|---|---|
| Entra | Cirreum.Authorization.Entra |
Azure AD / Entra ID JWT tokens |
| API Key | Cirreum.Authorization.ApiKey |
Static and dynamic API key authentication |
| Signed Request | Cirreum.Authorization.SignedRequest |
HMAC-signed requests for partners |
| External (BYOID) | Cirreum.Authorization.External |
Multi-tenant customer IdP tokens |
Installation
dotnet add package Cirreum.Runtime.Authorization
How It Works
Authorization Flow
Request arrives
│
▼
Routing determines endpoint
│
▼
Endpoint has [Authorize] or .RequireAuthorization()?
│
├── NO → Request proceeds (no authentication)
│
└── YES → Policy evaluated
│
▼
Policy specifies scheme?
│
├── YES → Use that scheme directly
│
└── NO → ForwardDefaultSelector routes dynamically
│
▼
Scheme handler authenticates
│
▼
Policy requirements checked (roles, claims)
Important: Authentication only occurs when an endpoint requires authorization. Anonymous endpoints never trigger scheme selection.
Dynamic Scheme Selection
The ForwardDefaultSelector examines request characteristics and routes to the appropriate handler:
1. Conflict check → Ambiguous indicators? → Reject (401)
2. API Key header → X-Api-Key present? → API Key handler
3. Signed Request → All 3 headers? → Signed Request handler
4. External (BYOID) → Tenant + Bearer? → External handler
5. JWT Bearer → Bearer token? → Entra handler (by audience)
→ Unrecognized audience → Reject (401)
6. No credentials → Nothing present → Skip (anonymous allowed)
Note: There is no silent fallback. If the selector detects authentication credentials but cannot determine the appropriate scheme, the request is rejected. This fail-closed behavior prevents credentials from being evaluated by an unrelated handler. Requests with no authentication indicators are allowed through for anonymous endpoints.
Conflict Detection
When a request contains conflicting authentication indicators, it's rejected rather than guessing:
❌ X-Api-Key + X-Tenant-Slug → Ambiguous (401)
✓ X-Api-Key only → API Key handler
✓ X-Tenant-Slug + Bearer → External handler
This prevents "scheme shopping" attacks where an attacker sends multiple credentials hoping one works.
Usage
Basic Setup
var builder = WebApplication.CreateBuilder(args);
// Registers all configured providers from appsettings.json
builder.AddAuthorization();
The authentication and authorization middleware is automatically configured by the Cirreum runtime - no need to call UseAuthentication() or UseAuthorization() manually.
Builder Pattern
The AddAuthorization method accepts an optional lambda for configuring additional authentication schemes via CirreumAuthorizationBuilder:
builder.AddAuthorization(auth => auth
.AddSignedRequest<TResolver>() // HMAC-signed requests
.AddDynamicApiKeys<TResolver>([]) // Database-backed API keys
.AddExternal<TResolver>() // Multi-tenant BYOID
)
.AddPolicy("MyPolicy", policy => ...); // Standard ASP.NET Core policies
This pattern:
- Keeps Cirreum-specific configuration grouped together
- Returns the standard
AuthorizationBuilderfor chaining policies - Prevents accidental use without first calling
AddAuthorization()
With External (BYOID) Authentication
builder.AddAuthorization(auth => auth
.AddExternal<DatabaseTenantResolver>()
)
.AddPolicy("TenantAccess", policy => {
policy
.AddAuthenticationSchemes(ExternalDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole("app:user");
});
With Dynamic API Keys
builder.AddAuthorization(auth => auth
.AddDynamicApiKeys<DatabaseApiKeyResolver>(
headers: ["X-Api-Key"],
options => options.WithCaching())
);
With Signed Request Authentication
builder.AddAuthorization(auth => auth
.AddSignedRequest<DatabaseSignedRequestResolver>()
.AddSignatureValidationEvents<RateLimitingEvents>()
);
Combined Setup (All Providers)
builder.AddAuthorization(auth => auth
// External (BYOID) for customer IdPs
.AddExternal<DatabaseTenantResolver>()
// Dynamic API keys for internal services
.AddDynamicApiKeys<DatabaseApiKeyResolver>(
headers: ["X-Api-Key"],
options => options.WithCaching())
// Signed requests for external partners
.AddSignedRequest<DatabaseSignedRequestResolver>()
.AddSignatureValidationEvents<RateLimitingEvents>()
)
// Custom policies via standard ASP.NET Core AuthorizationBuilder
.AddPolicy("TenantAccess", policy => {
policy
.AddAuthenticationSchemes(ExternalDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole("tenant:user");
})
.AddPolicy("PartnerAccess", policy => {
policy
.AddAuthenticationSchemes(SignedRequestDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole("partner");
});
Configuration
appsettings.json
{
"Cirreum": {
"Authorization": {
"PrimaryScheme": "WorkforceUsers",
"Providers": {
"Entra": {
"Instances": {
"WorkforceUsers": {
"Enabled": true,
"Audience": "api://your-app-id",
"TenantId": "your-tenant-id"
}
}
},
"ApiKey": {
"Instances": {
"InternalService": {
"Enabled": true,
"HeaderName": "X-Api-Key",
"ClientId": "internal-svc",
"Roles": ["App.System"]
}
}
},
"External": {
"Instances": {
"default": {
"Enabled": true,
"TenantIdentifierSource": "Header",
"TenantHeaderName": "X-Tenant-Slug",
"RequireHttpsMetadata": true
}
}
}
}
}
}
}
Configuration Reference
| Setting | Required | Description |
|---|---|---|
PrimaryScheme |
Yes | The Entra instance name used exclusively for the System policy. Must match one of your configured Entra instance names. |
Providers |
Yes | Provider configurations (Entra, ApiKey, External, etc.) |
Important: PrimaryScheme is used only for the System authorization policy. It is not a fallback for unmatched requests. If the dynamic selector cannot determine a scheme, the request is rejected.
Authorization Policies
Predefined Policies
The library includes hierarchical role-based policies:
| Policy | Scheme | Roles | Description |
|---|---|---|---|
System |
Primary only | App.System |
Highest privilege, restricted to primary Entra instance |
StandardAdmin |
Dynamic | App.System, App.Admin |
Administrative access |
StandardManager |
Dynamic | + App.Manager |
Management access |
StandardAgent |
Dynamic | + App.Agent |
Agent/service access |
StandardInternal |
Dynamic | + App.Internal |
Internal user access |
Standard |
Dynamic | + App.User |
All authenticated users |
The System policy is special - it only accepts authentication from the primary Entra instance (configured via PrimaryScheme). This ensures system-level operations cannot be performed via API keys or other mechanisms.
All other policies use the dynamic scheme, allowing authentication via any configured provider.
Cross-Scheme Authorization
Policies using the dynamic scheme work across all authentication types. The auth_scheme claim identifies which handler authenticated the request.
// Accept ANY configured authentication method
// The dynamic scheme routes to the appropriate handler based on request indicators
builder.AddAuthorization()
.AddPolicy("PartnerAccess", policy => {
policy
.AddAuthenticationSchemes(AuthorizationSchemes.Dynamic)
.RequireAuthenticatedUser()
.RequireRole("partner");
});
Scheme-Specific Authorization
To restrict a policy to a specific authentication method, use that scheme directly:
// Only accept External (BYOID) authentication
builder.AddAuthorization()
.AddPolicy("TenantOnly", policy => {
policy
.AddAuthenticationSchemes(ExternalDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole("tenant:user");
});
// Only accept API key authentication
builder.AddAuthorization()
.AddPolicy("ServiceOnly", policy => {
policy
.AddAuthenticationSchemes("Header:X-Api-Key")
.RequireAuthenticatedUser()
.RequireRole("App.System");
});
// Only accept Signed Request authentication
builder.AddAuthorization()
.AddPolicy("PartnerOnly", policy => {
policy
.AddAuthenticationSchemes(SignedRequestDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole("partner");
});
Available Scheme Constants
| Constant | Value | Description |
|---|---|---|
AuthorizationSchemes.Dynamic |
DynamicScheme |
Routes to appropriate handler based on request |
ExternalDefaults.AuthenticationScheme |
Byoid |
External (BYOID) authentication only |
SignedRequestDefaults.AuthenticationScheme |
SignedRequest |
Signed request authentication only |
"Header:{HeaderName}" |
e.g., Header:X-Api-Key |
API key authentication for specific header |
Scheme Selection Reference
| Request Indicators | Selected Scheme |
|---|---|
X-Api-Key + X-Tenant-Slug (header) |
Rejected (ambiguous) |
X-Api-Key |
API Key handler |
X-Client-Id + X-Timestamp + X-Signature |
Signed Request handler |
X-Tenant-Slug + Authorization: Bearer |
External (BYOID) handler |
Authorization: Bearer (recognized audience) |
Entra handler |
Authorization: Bearer (unrecognized audience) |
Rejected (ambiguous) |
| No credentials | Skipped (anonymous allowed) |
Security Considerations
Fail-Closed Design
The dynamic selector never silently falls back to an unrelated scheme when credentials are present:
- Unrecognized JWT audience → Rejected (not sent to random Entra instance)
- Conflicting indicators → Rejected (not guessed)
- No credentials at all → Skipped (allows anonymous endpoints to work)
This prevents credential confusion attacks where tokens or keys might accidentally be validated by the wrong handler.
Authentication vs Authorization
- Authentication (who are you?) - Only triggered when an endpoint requires authorization
- Authorization (what can you do?) - Policy requirements checked after authentication
Anonymous endpoints ([AllowAnonymous]) bypass the entire authentication system.
Scheme Priority
The selection order matters for security:
- Conflict detection first - Prevents ambiguous requests from authenticating
- Most specific matches - API key headers checked before generic Bearer tokens
- Audience matching - JWT tokens routed by audience claim
- Rejection last - No match means rejection, not fallback
Role Normalization
All providers normalize roles to a common format, enabling cross-scheme policies. The auth_scheme claim lets you distinguish authentication methods when needed.
Documentation
- Authentication Architecture - Comprehensive security guide covering OAuth/OIDC vs Signed Request trade-offs, partner security considerations, and RFC compliance
Contribution Guidelines
- Be conservative with new abstractions - The API surface must remain stable
- Limit dependency expansion - Only foundational, version-stable dependencies
- Favor additive, non-breaking changes - Breaking changes ripple through the ecosystem
- Include thorough unit tests - All patterns should be independently testable
- Document architectural decisions - Context and reasoning for future maintainers
License
This project is licensed under the MIT License - see the LICENSE file for details.
Cirreum Foundation Framework Layered simplicity for modern .NET
| 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
- Cirreum.Authorization.ApiKey (>= 1.0.7)
- Cirreum.Authorization.Entra (>= 1.0.11)
- Cirreum.Authorization.External (>= 1.0.2)
- Cirreum.Authorization.SignedRequest (>= 1.0.5)
- Cirreum.Core (>= 1.0.39)
- Cirreum.Runtime.AuthorizationProvider (>= 1.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.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.45 | 73 | 4/28/2026 |
| 1.0.44 | 78 | 4/27/2026 |
| 1.0.43 | 76 | 4/26/2026 |
| 1.0.42 | 101 | 4/15/2026 |
| 1.0.41 | 101 | 4/13/2026 |
| 1.0.40 | 96 | 4/13/2026 |
| 1.0.39 | 104 | 4/10/2026 |
| 1.0.38 | 101 | 3/25/2026 |
| 1.0.37 | 104 | 3/21/2026 |
| 1.0.36 | 89 | 3/21/2026 |
| 1.0.35 | 102 | 3/17/2026 |
| 1.0.34 | 93 | 3/16/2026 |
| 1.0.33 | 94 | 3/14/2026 |
| 1.0.32 | 97 | 3/13/2026 |
| 1.0.31 | 102 | 3/10/2026 |
| 1.0.30 | 94 | 3/9/2026 |
| 1.0.29 | 119 | 2/5/2026 |
| 1.0.28 | 113 | 1/31/2026 |
| 1.0.27 | 108 | 1/31/2026 |
| 1.0.26 | 106 | 1/30/2026 |