Cirreum.Authentication.External
1.1.0
See the version list below for details.
dotnet add package Cirreum.Authentication.External --version 1.1.0
NuGet\Install-Package Cirreum.Authentication.External -Version 1.1.0
<PackageReference Include="Cirreum.Authentication.External" Version="1.1.0" />
<PackageVersion Include="Cirreum.Authentication.External" Version="1.1.0" />
<PackageReference Include="Cirreum.Authentication.External" />
paket add Cirreum.Authentication.External --version 1.1.0
#r "nuget: Cirreum.Authentication.External, 1.1.0"
#:package Cirreum.Authentication.External@1.1.0
#addin nuget:?package=Cirreum.Authentication.External&version=1.1.0
#tool nuget:?package=Cirreum.Authentication.External&version=1.1.0
Cirreum Authentication - External (BYOID)
Multi-tenant external IdP (BYOID) authentication scheme for the Cirreum framework
Overview
Cirreum.Authentication.External enables a single API to accept JWT bearer tokens from multiple customer Identity Providers (Okta, Auth0, customer Entra tenants, etc.) without federating those IdPs into yours. The customer's existing IdP issues tokens; your API validates them per-tenant using the resolved tenant configuration.
Use this package when your customers want to sign in to your API with their own IdP credentials. Use Cirreum.Authentication.Oidc or Cirreum.Authentication.Entra instead when you have a single, configured-by-you IdP.
How it works
- The inbound request carries a tenant indicator — a header (
X-Tenant-Slug), a path segment (/tenants/{slug}/...), or a subdomain ({tenant}.api.example.com). - The package's
IExternalTenantResolver(your implementation) maps that indicator to the tenant's configuration: OIDC metadata address, valid audiences, etc. - JWKS metadata is fetched from the tenant's
.well-known/openid-configurationand cached perJwksCacheDurationMinutes. - The inbound
Authorization: Bearer {jwt}is validated against the resolved per-tenant configuration. - On success, the
ClaimsPrincipalreflects the tenant's claims.
The dynamic forward resolver picks this scheme (via ExternalAuthenticationSchemeSelector) when both a tenant indicator and a Bearer token are present on the request.
One scheme, many tenants
External is a single-instance provider: it serves every tenant through one scheme, resolving each tenant's issuer at request time. Per-tenant variance belongs in your IExternalTenantResolver, not in additional configured instances — a second enabled instance fails composition with a diagnostic.
As with every Cirreum authentication provider, the configured instance key is the scheme name. That name is what [Authorize(AuthenticationSchemes = ...)] matches and what an IApplicationUserResolver.Scheme must return to be dispatched for External-authenticated requests.
Installation
dotnet add package Cirreum.Authentication.External
Configuration
{
"Cirreum": {
"Authentication": {
"Providers": {
"External": {
"Instances": {
"Byoid": {
"Enabled": true,
"TenantIdentifierSource": "Header",
"TenantHeaderName": "X-Tenant-Slug",
"JwksCacheDurationMinutes": 60,
"RequireHttpsMetadata": true,
"TenantNotFoundBehavior": "Reject",
"ClockSkewSeconds": 30,
"DetailedErrors": false
}
}
}
}
}
}
}
The instance key (Byoid above) becomes the scheme name; ExternalDefaults.AuthenticationScheme is that conventional key. Do not set a Scheme value in configuration — the registrar derives it from the key and fails loudly on a mismatch.
Then register your tenant resolver inside the AddAuthentication(...) composition callback:
builder.AddAuthentication(auth => auth
.AddExternalTenantResolver<MyTenantResolver>());
The resolver is registered scoped by default, so it can consume a scoped store (a DbContext, a unit of work, a repository). A resolver that holds its own cache and takes no scoped dependencies can opt in:
auth.AddExternalTenantResolver<MyCachingResolver>(lifetime: ServiceLifetime.Singleton);
Implementing the tenant resolver
public sealed class MyTenantResolver(IDbConnection db) : IExternalTenantResolver {
public async Task<ExternalTenantConfig?> ResolveAsync(
ExternalResolutionContext context,
CancellationToken cancellationToken = default) {
var row = await db.QueryFirstOrDefaultAsync(
"SELECT Slug, IsActive, MetadataUrl, Audience FROM Tenants WHERE Slug = @Slug",
new { Slug = context.TenantSlug });
if (row is null) {
return null;
}
return new ExternalTenantConfig {
Slug = row.Slug,
IsEnabled = row.IsActive,
MetadataAddress = row.MetadataUrl,
ValidAudiences = [row.Audience]
};
}
}
ExternalResolutionContext also carries the token's issuer and audience, so a resolver can key on those instead of — or alongside — the tenant slug.
What changed
Selector-based dispatch
ExternalAuthenticationSchemeSelector implements ISchemeSelector at SchemeSelectorPriority.External, ahead of the generic JwtAudienceSchemeSelector, so the stricter "tenant indicator and Bearer both required" probe runs first. The dynamic forward resolver picks External when:
- A tenant indicator is present (per configured
TenantIdentifierSource) - An
Authorization: Bearerheader is present
The legacy static ExternalSchemeSelector helper class is retired. Detection logic survives as static methods on the new instance class for apps that compose conflict-detection at startup.
Security considerations
- Tenant configuration trust — your
IExternalTenantResolvermust return only verified, currently-active tenant configurations. Cache invalidation on tenant deactivation is your responsibility. - HTTPS enforcement —
RequireHttpsMetadata: true(default) blocks JWKS fetches over plain HTTP. - Clock skew —
ClockSkewSeconds: 30is a reasonable default; tighten for high-trust tenants. - TenantNotFoundBehavior —
Rejectis the safe default;Fallbackis only appropriate when your fallback is your own IdP under your control. - Token type — tokens with no
typheader are rejected, and anid_tokenis never accepted as an access token. SetRequireAccessTokenTypeon the resolved tenant config to additionally requireat+jwt. - Authorized party — populate
AllowedClientIdson the resolved tenant config to restrict which of a tenant's client applications may call your API (matched againstazp, thenclient_id).
License
MIT — see LICENSE.
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.AuthenticationProvider (>= 1.4.1)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 8.21.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Cirreum.Authentication.External:
| Package | Downloads |
|---|---|
|
Cirreum.Runtime.Authentication
App-facing umbrella for the Authentication pillar. Provides AddAuthentication() and the CirreumAuthenticationBuilder type. Transitively references all six Cirreum.Authentication.* schemes (ApiKey, SignedRequest, SessionTicket, OIDC, Entra, External) — apps install this single package to get the full Authentication track. |
GitHub repositories
This package is not used by any popular GitHub repositories.