Cirreum.Runtime.Authorization
1.0.29
dotnet add package Cirreum.Runtime.Authorization --version 1.0.29
NuGet\Install-Package Cirreum.Runtime.Authorization -Version 1.0.29
<PackageReference Include="Cirreum.Runtime.Authorization" Version="1.0.29" />
<PackageVersion Include="Cirreum.Runtime.Authorization" Version="1.0.29" />
<PackageReference Include="Cirreum.Runtime.Authorization" />
paket add Cirreum.Runtime.Authorization --version 1.0.29
#r "nuget: Cirreum.Runtime.Authorization, 1.0.29"
#:package Cirreum.Runtime.Authorization@1.0.29
#addin nuget:?package=Cirreum.Runtime.Authorization&version=1.0.29
#tool nuget:?package=Cirreum.Runtime.Authorization&version=1.0.29
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 with unrecognized or conflicting credentials
- 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: The authentication middleware runs on every request due to the default scheme configuration. For requests without credentials, the Anonymous scheme returns NoResult(), allowing unauthenticated access to endpoints that don't require authorization.
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 |
AuthorizationSchemes.Anonymous |
Anonymous |
Returns NoResult() for requests with no credentials |
AuthorizationSchemes.Ambiguous |
AmbiguousRequest |
Rejects requests with unrecognized/conflicting credentials |
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]) or endpoints without [Authorize] proceed when no credentials are present, as the Anonymous scheme returns NoResult() rather than failing.
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.3)
- Cirreum.Authorization.SignedRequest (>= 1.0.5)
- Cirreum.Core (>= 1.0.40)
- 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.29 | 98 | 2/5/2026 |
| 1.0.28 | 100 | 1/31/2026 |
| 1.0.27 | 94 | 1/31/2026 |
| 1.0.26 | 92 | 1/30/2026 |
| 1.0.25 | 95 | 1/30/2026 |
| 1.0.24 | 89 | 1/28/2026 |
| 1.0.23 | 88 | 1/21/2026 |
| 1.0.22 | 94 | 1/12/2026 |
| 1.0.21 | 97 | 1/11/2026 |
| 1.0.20 | 100 | 1/6/2026 |
| 1.0.19 | 95 | 1/5/2026 |
| 1.0.18 | 97 | 1/2/2026 |
| 1.0.17 | 107 | 1/1/2026 |
| 1.0.16 | 103 | 12/30/2025 |
| 1.0.15 | 88 | 12/29/2025 |
| 1.0.14 | 92 | 12/29/2025 |
| 1.0.12 | 181 | 12/22/2025 |
| 1.0.11 | 136 | 12/20/2025 |
| 1.0.10 | 172 | 12/20/2025 |
| 1.0.9 | 210 | 12/19/2025 |