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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Cirreum.Runtime.Authorization" Version="1.0.29" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Cirreum.Runtime.Authorization" Version="1.0.29" />
                    
Directory.Packages.props
<PackageReference Include="Cirreum.Runtime.Authorization" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Cirreum.Runtime.Authorization --version 1.0.29
                    
#r "nuget: Cirreum.Runtime.Authorization, 1.0.29"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Cirreum.Runtime.Authorization@1.0.29
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Cirreum.Runtime.Authorization&version=1.0.29
                    
Install as a Cake Addin
#tool nuget:?package=Cirreum.Runtime.Authorization&version=1.0.29
                    
Install as a Cake Tool

Cirreum.Runtime.Authorization

NuGet Version NuGet Downloads GitHub Release License .NET

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 AuthorizationBuilder for 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:

  1. Conflict detection first - Prevents ambiguous requests from authenticating
  2. Most specific matches - API key headers checked before generic Bearer tokens
  3. Audience matching - JWT tokens routed by audience claim
  4. 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

  1. Be conservative with new abstractions - The API surface must remain stable
  2. Limit dependency expansion - Only foundational, version-stable dependencies
  3. Favor additive, non-breaking changes - Breaking changes ripple through the ecosystem
  4. Include thorough unit tests - All patterns should be independently testable
  5. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed