Cirreum.Authorization.Oidc 1.0.10

Suggested Alternatives

Cirreum.Authentication.Oidc

dotnet add package Cirreum.Authorization.Oidc --version 1.0.10
                    
NuGet\Install-Package Cirreum.Authorization.Oidc -Version 1.0.10
                    
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.Authorization.Oidc" Version="1.0.10" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Cirreum.Authorization.Oidc" Version="1.0.10" />
                    
Directory.Packages.props
<PackageReference Include="Cirreum.Authorization.Oidc" />
                    
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.Authorization.Oidc --version 1.0.10
                    
#r "nuget: Cirreum.Authorization.Oidc, 1.0.10"
                    
#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.Authorization.Oidc@1.0.10
                    
#: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.Authorization.Oidc&version=1.0.10
                    
Install as a Cake Addin
#tool nuget:?package=Cirreum.Authorization.Oidc&version=1.0.10
                    
Install as a Cake Tool

Cirreum.Authorization.Oidc

NuGet Version NuGet Downloads GitHub Release License .NET

Generic OIDC authentication for any OpenID Connect-compliant issuer — for both Web APIs and interactive Web Apps.

Overview

Cirreum.Authorization.Oidc is an authorization provider for the Cirreum framework that covers two scenarios from a single registrar, with no vendor-specific SDK dependency:

  • Web API (resource server) — validates JWT bearer tokens locally using ASP.NET Core's AddJwtBearer().
  • Web App (Blazor Web App, MVC, Razor Pages) — interactive sign-in via Authorization Code
    • PKCE using AddOpenIdConnect() with a cookie session.

The host picks the path. A Minimal API project gets the JWT bearer wiring; a traditional Blazor Web App gets the cookie + OIDC wiring. The configuration shape is the same for both.

When to use

Token source Use this? Why
Descope Yes Standard OIDC issuer with .well-known/openid-configuration
Auth0 Yes Standard OIDC
Okta Yes Standard OIDC
Keycloak Yes Standard OIDC
Entra External ID (CIAM) Yes Standard OIDC issuer; no Microsoft-specific features needed
Any .well-known/openid-configuration issuer Yes That's all this provider needs
Entra Workforce (employees) Use Cirreum.Authorization.Entra Needs Microsoft.Identity.Web for Graph, OBO, sovereign clouds
Customer-owned IdPs (B2B SaaS, dynamic tenants) Use Cirreum.Authorization.External Needs dynamic tenant resolution at runtime

How it works

Web API path (JWT bearer)

There's no network call to the IdP on every request:

1. Startup (or first request):
   ├─ AddJwtBearer middleware fetches  {Authority}/.well-known/openid-configuration
   ├─ Reads jwks_uri → fetches the IdP's signing keys (JWKS)
   └─ Caches the keys in memory (auto-refreshed every 24 h / on unknown kid)

2. Every request:
   ├─ Extract Authorization: Bearer <jwt>
   ├─ Validate signature against cached JWKS
   ├─ Validate issuer (iss) matches Authority
   ├─ Validate audience (aud) contains Audience
   ├─ Validate lifetime (nbf / exp)
   └─ Optional: validate required scopes (scp / scope claim)

Everything happens locally in the API process. The IdP is only contacted for metadata (once at startup) and for signing-key refreshes on kid rotation. This is what makes JWT-based APIs fast and IdP-independent.

Web App path (Auth Code + PKCE)

1. User hits a protected page:
   └─ OIDC middleware redirects to  {Authority}/authorize
      with response_type=code, PKCE challenge, scopes={openid profile + RequiredScopes}

2. User authenticates at the IdP, browser redirects back to /signin-oidc with a code

3. Middleware exchanges code + PKCE verifier at  {Authority}/token
   ├─ Receives id_token + access_token (+ refresh_token if requested)
   ├─ Validates id_token signature/issuer/audience/lifetime
   └─ Issues an authentication cookie (and persists tokens via SaveTokens = true)

4. Subsequent requests:
   └─ Cookie carries the principal — no IdP round-trip until cookie expiry

The registrar registers a "Cookies" cookie scheme automatically — you don't need to call AddCookie() yourself. ID-token signature validation uses the same JWKS caching as the API path.

Installation

dotnet add package Cirreum.Authorization.Oidc

Configuration

The provider supports multiple named instances, each validating tokens from a different OIDC issuer. Register them under Cirreum:Authorization:Providers:Oidc:Instances:

{
  "Cirreum": {
    "Authorization": {
      "Providers": {
        "Oidc": {
          "Instances": {
            "descope": {
              "Enabled": true,
              "Authority": "https://api.descope.com/<ProjectId>",
              "Audience": "<ProjectId>"
            },
            "auth0": {
              "Enabled": true,
              "Authority": "https://myapp.auth0.com",
              "Audience": "https://my-api"
            }
          }
        }
      }
    }
  }
}

The same configuration shape drives both the Web API path and the Web App path; the host project picks which path runs. For Web App scenarios, Audience is reused as the OIDC client_id (this overload is being split into a dedicated ClientId property in a future version — see Notes for Web App scenarios).

Instance settings

Property Required Description
Enabled Yes Enable/disable this instance without removing the block
Authority Yes OIDC issuer URL. Must publish .well-known/openid-configuration
Audience Yes Web API: expected JWT aud claim. Web App: OIDC client_id
RequiredScopes No Web API: scopes that must be present in the token (rejected at validation). Web App: scopes appended to the authorize request at sign-in time
TokenValidationParameters No Any property on Microsoft.IdentityModel.Tokens.TokenValidationParameters — e.g. ClockSkew, NameClaimType, RoleClaimType, ValidAlgorithms
MapInboundClaims No Whether to map short JWT claim names to legacy Microsoft URIs. Defaults to false

Anything else on JwtBearerOptions or OpenIdConnectOptions (including ClientSecret, client certificates, CallbackPath, custom events) can be set by adding it to the same config section — the registrar binds the section onto the options after applying defaults. For confidential-client web apps, put ClientSecret in user-secrets or Key Vault, not appsettings.

Built-in defaults

The registrar applies these defaults so a minimal Authority + Audience config "just works" for the common case. Each default can be overridden in configuration.

Both paths:

Setting Default Why
RequireHttpsMetadata true Discovery document must be served over HTTPS. Override only for local dev tunnels
MapInboundClaims false Keeps JWT short claim names (sub, name, roles) rather than mapping to legacy Microsoft URIs
TokenValidationParameters.NameClaimType "name" Standard OIDC core claim (profile scope). User.Identity.Name resolves out of the box
TokenValidationParameters.RoleClaimType "roles" Common role-claim convention. [Authorize(Roles = "...")] and User.IsInRole(...) work without extra config

Web App path only:

Setting Default Why
ResponseType "code" Authorization Code flow — implicit and hybrid are not used
UsePkce true PKCE protects the code exchange. Required for public clients, recommended for confidential clients
SaveTokens true Persists id/access/refresh tokens in the auth cookie so the app can call downstream APIs without re-prompting
Scope [ "openid", "profile" ] Standard OIDC core scopes. RequiredScopes are appended additively
SignInScheme / SignOutScheme "Cookies" A cookie scheme is registered automatically — no AddCookie() call needed

Consumers whose IdP uses different claim names can override via config — see Full example — overriding TokenValidationParameters below.

Instance key = scheme name

The key under Instances: (descope, auth0 in the example above) is the ASP.NET Core authentication scheme name. It's auto-populated into Scheme during registration — do not set Scheme in configuration. See the base package README for more on this convention.

Full example — overriding TokenValidationParameters

This example shows what's possible when the defaults don't fit — e.g., an IdP that emits namespaced role claims (Auth0), a groups claim instead of roles (Okta), or you want email as the display name:

{
  "Cirreum": {
    "Authorization": {
      "Providers": {
        "Oidc": {
          "Instances": {
            "auth0": {
              "Enabled": true,
              "Authority": "https://myapp.auth0.com",
              "Audience": "https://my-api",
              "TokenValidationParameters": {
                "ClockSkew": "00:00:02",
                "NameClaimType": "email",
                "RoleClaimType": "https://myapp/roles",
                "ValidAlgorithms": [ "RS256" ]
              }
            }
          }
        }
      }
    }
  }
}

Anything on JwtBearerOptions, OpenIdConnectOptions, or TokenValidationParameters can be set from config — the registrar applies its defaults first, then binds the instance section on top, then re-pins Authority and Audience/ClientId from the typed settings.

Notes for Web App scenarios

A traditional Blazor Web App (.NET 8+ server-interactive), MVC app, or Razor Pages app uses the Web App path automatically — there's nothing Blazor-specific in the registrar. The AuthorizeRouteView / CascadingAuthenticationState / AuthenticationStateProvider machinery in Blazor sits on top of the standard cookie principal that this registrar produces.

Confidential vs public clients. A traditional server-rendered web app is normally a confidential client and needs a ClientSecret. Add it under the instance section (via user-secrets / Key Vault, not appsettings):

"descope": {
  "Authority": "https://api.descope.com/<ProjectId>",
  "Audience": "<ProjectId>",
  "ClientSecret": "..."
}

Public-client PKCE-only flows are also supported if your IdP allows them — just omit ClientSecret.

Audience overload. On the Web App path, Audience is currently assigned to OpenIdConnectOptions.ClientId. A future version of Cirreum.AuthorizationProvider will introduce a dedicated ClientId property; until then, populate Audience with your OIDC client_id for Web App instances.

Authorization

The library validates the token. It does not enforce per-endpoint authorization — that's ASP.NET Core's policy layer.

Role-based (typical)

For most Cirreum-built apps, role-based authorization is the primary enforcement mechanism. Set RoleClaimType (see above) to whatever your IdP names the roles claim, then use the standard attributes:

app.MapGet("/admin", [Authorize(Roles = "admin")] () => "ok");
app.MapGet("/users", [Authorize(Roles = "user,admin")] () => "ok");

Scope-based (advanced)

If you want per-endpoint scope enforcement, use ASP.NET Core policies:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("api:read",  p => p.RequireClaim("scp", "api:read"))
    .AddPolicy("api:write", p => p.RequireClaim("scp", "api:write"));

app.MapGet("/data", [Authorize(Policy = "api:read")] () => ...);

Advanced: scope enforcement

Most first-party Cirreum apps (single SPA + single API) rely on audience + signature + roles and do not need RequiredScopes. Scope enforcement is most useful when:

  • Tokens may come from multiple clients with different permission levels
  • You have third-party integrations with delegated permissions (e.g., Descope Inbound Apps)
  • You want an explicit contract between API and client even when audience already isolates

When you do enable it, RequiredScopes acts at the scheme level: every request that authenticates through this scheme must carry all listed scopes. Tokens missing any of them are rejected with 401.

How the IdP decides which scopes are in the token

The scope claim (scp or scope) is not a self-service field. It's emitted by the IdP only if it approved the requesting client for those scopes at issuance time. Each IdP has its own model for gating this:

IdP Scope approval model
Entra Workforce / External API app registration exposes scopes; Client app has delegated permissions; admin/user consent
Auth0 API resource declares permissions; Applications are authorized for specific permissions
Okta Authorization Server defines scopes; Apps are assigned via scope groups
Descope (default OIDC app) Pass-through of standard OIDC scopes; no app-level scope authorization. Use Inbound Apps for delegated-access scopes

Before requiring a scope in config, confirm your IdP is actually issuing it. Decode a fresh access token at jwt.ms and look for the scope in scp or scope. If it's missing, the IdP doesn't emit it and RequiredScopes will reject every request.

Scope validation semantics

  • AND semantics — all listed scopes must be present. There is no "any of" variant; use ASP.NET Core policies for that.
  • Both claim names are checkedscp (space-delimited, common in Microsoft/Entra) and scope (common elsewhere). Values from both claims are unioned.
  • Case-insensitive — scope comparisons use StringComparer.OrdinalIgnoreCase.

Example

"entraExternal": {
  "Enabled": true,
  "Authority": "https://myapp.ciamlogin.com/<tenant-id>/v2.0",
  "Audience": "<client-id>",
  "RequiredScopes": [ "access_as_user" ]
}

Registration

The provider is registered automatically by the Cirreum authorization pipeline via configuration. No manual registration code is needed beyond the standard hosting setup:

// In HostingExtensions (framework-level)
builder.RegisterAuthorizationProvider<
    OidcAuthorizationRegistrar,
    OidcAuthorizationSettings,
    OidcAuthorizationInstanceSettings>(authenticationBuilder);

Each enabled instance under Providers.Oidc.Instances is registered as a distinct ASP.NET Core authentication scheme (named after the instance key). At request time, Cirreum.Runtime.Authorization dispatches tokens to the correct scheme based on the aud claim.

Contribution Guidelines

  1. Be conservative with new abstractions The API surface must remain stable and meaningful.

  2. Limit dependency expansion Only add foundational, version-stable dependencies.

  3. Favor additive, non-breaking changes Breaking changes ripple through the entire ecosystem.

  4. Include thorough unit tests All primitives and patterns should be independently testable.

  5. Document architectural decisions Context and reasoning should be clear for future maintainers.

  6. Follow .NET conventions Use established patterns from Microsoft.Extensions.* libraries.

Versioning

Cirreum.Authorization.Oidc follows Semantic Versioning:

  • Major - Breaking API changes
  • Minor - New features, backward compatible
  • Patch - Bug fixes, backward compatible

Given its foundational role, major version bumps are rare and carefully considered.

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 (1)

Showing the top 1 NuGet packages that depend on Cirreum.Authorization.Oidc:

Package Downloads
Cirreum.Runtime.Authorization

The Runtime Authorization configuration.

GitHub repositories

This package is not used by any popular GitHub repositories.