Cirreum.IdentityProvider 1.0.5

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

Cirreum Identity Provider

NuGet Version NuGet Downloads GitHub Release License .NET

Identity-provider framework for Cirreum — the Core-layer library shared by all Cirreum identity-provider integrations.

Overview

Cirreum.IdentityProvider is the Core-layer library for the Cirreum Identity provider family. It defines the shared registrar, configuration types, and user-provisioning contracts that concrete identity-provider packages (OIDC webhook callbacks, Entra External ID claims issuance, etc.) build upon.

Apps do not reference this package directly — they install a Runtime Extensions package such as Cirreum.Runtime.Identity.Oidc, Cirreum.Runtime.Identity.EntraExternalId, or the umbrella Cirreum.Runtime.Identity. This package flows in transitively.

Provider pattern

All Cirreum Identity providers share a common shape:

  • The IdP calls back into the app during sign-in with a payload describing the authenticating user.
  • The app's IUserProvisioner decides whether the user is allowed in and what roles to embed in the issued token.
  • A concrete provisioning provider (e.g. OIDC webhook, Entra External ID) wires up the HTTP endpoint, validates the inbound request, builds a ProvisionContext, resolves the keyed provisioner, and returns the appropriate response format for that IdP.

Two-phase registration

Identity providers must register both DI services and HTTP endpoints. The base IdentityProviderRegistrar<,> therefore exposes two phases:

  1. Services phase (before builder.Build()) — Register(providerSettings, services, configuration) validates settings, guards against duplicate registration, auto-populates Source from the instance key, and delegates instance-specific DI registration to RegisterProvisioner.
  2. Endpoints phase (after builder.Build()) — Map(providerSettings, endpoints) walks enabled instances and delegates per-instance endpoint mapping to MapProvisioner.

The provider-specific Runtime Extensions package (Cirreum.Runtime.Identity.*) surfaces these via app-facing extension methods — ultimately composing into builder.AddIdentity() / app.MapIdentity() at the umbrella level.

Configuration shape

{
  "Cirreum": {
    "Identity": {
      "Providers": {
        "Oidc": {
          "Instances": {
            "clientA_descope": {
              "Enabled": true,
              "Route": "/auth/clientA/provision",
              "...": "provider-specific settings"
            },
            "clientB_descope": {
              "Enabled": true,
              "Route": "/auth/clientB/provision",
              "...": "provider-specific settings"
            }
          }
        },
        "EntraExternalId": {
          "Instances": {
            "primary": {
              "Enabled": true,
              "Route": "/auth/entra/provision",
              "...": "provider-specific settings"
            }
          }
        }
      }
    }
  }
}

Instance key = Source name

The instance key under Instances: serves double duty: it is both the logical instance name and the Source value stamped into ProvisionContext by the callback handler. The key is also used as the keyed DI key under which IUserProvisioner is registered, so multi-instance / multi-IdP apps can register one provisioner per source and have the correct one resolved automatically.

Do not set Source in configuration. It is auto-populated from the instance key during registration. If a mismatched value is detected, registration fails with an InvalidOperationException rather than silently overwriting.

Key types

Registrar (namespace Cirreum.Identity)

Type Purpose
IdentityProviderRegistrar<TSettings, TInstanceSettings> Abstract base for all identity provider registrars. Two-phase Register + Map. Concrete Infrastructure packages implement RegisterProvisioner and MapProvisioner.

Configuration (namespace Cirreum.Identity.Configuration)

Type Purpose
IdentityProviderSettings<TInstanceSettings> Base settings container — Dictionary<string, TInstanceSettings> Instances.
IdentityProviderInstanceSettings Base instance settings — Source, Enabled, Route.

Provisioning contracts (namespace Cirreum.Identity.Provisioning)

Type Purpose
IUserProvisioner The app's hook into the pre-token callback. Returns ProvisionResult.Allow(...) or ProvisionResult.Deny().
UserProvisionerBase<TUser> Abstract generic base — orchestrates returning-user lookup, delegates onboarding to ProvisionNewUserAsync. Use directly for custom / hybrid onboarding.
InvitationUserProvisionerBase<TUser> Specialization for invitation-based onboarding. Abstract RedeemInvitationAsync.
SelfServiceUserProvisionerBase<TUser> Specialization for self-service onboarding. Abstract CreateSelfServiceUserAsync.
ProvisionContext Callback payload: Source, ExternalUserId, CorrelationId, ClientAppId, Email.
ProvisionResult Discriminated outcome — Allowed(roles) or Denied.
IProvisionedUser Constraint on the app's user entity — exposes ExternalUserId + Roles.
IPendingInvitation Modeling guide for invitation entities (used by invitation-based provisioners).

Implementing a provisioner

Consumer apps implement IUserProvisioner via the base that matches the onboarding model for a given instance. One concrete class per configured instance.

Invitation-based

using Cirreum.Identity.Provisioning;

public sealed class ClientBBorrowerProvisioner(AppDbContext db)
    : InvitationUserProvisionerBase<BorrowerUser> {

    protected override Task<BorrowerUser?> FindUserAsync(
        string externalUserId, CancellationToken ct) =>
        db.Users.FirstOrDefaultAsync(u => u.ExternalUserId == externalUserId, ct);

    protected override async Task<BorrowerUser?> RedeemInvitationAsync(
        string email, string externalUserId, CancellationToken ct) {
        // atomically find, validate, and claim the invitation; create user record
        // (implementation-specific — see IPendingInvitation for the expected shape)
    }
}

Self-service

using Cirreum.Identity.Provisioning;

public sealed class ClientABorrowerProvisioner(AppDbContext db)
    : SelfServiceUserProvisionerBase<BorrowerUser> {

    protected override Task<BorrowerUser?> FindUserAsync(
        string externalUserId, CancellationToken ct) =>
        db.Users.FirstOrDefaultAsync(u => u.ExternalUserId == externalUserId, ct);

    protected override async Task<BorrowerUser?> CreateSelfServiceUserAsync(
        ProvisionContext context, CancellationToken ct) {
        var user = new BorrowerUser {
            ExternalUserId = context.ExternalUserId,
            Email = context.Email,
            Roles = [BorrowerRoles.Default],
        };
        db.Users.Add(user);
        await db.SaveChangesAsync(ct);
        return user;
    }
}

Custom / hybrid orchestration

For applications that don't fit either preset (e.g. try invitation first, fall back to self-service), derive directly from UserProvisionerBase<TUser> and implement both FindUserAsync and ProvisionNewUserAsync:

using Cirreum.Identity.Provisioning;

public sealed class HybridProvisioner(AppDbContext db, IInvitationService invitations)
    : UserProvisionerBase<AppUser> {

    protected override Task<AppUser?> FindUserAsync(
        string externalUserId, CancellationToken ct) =>
        db.Users.FirstOrDefaultAsync(u => u.ExternalUserId == externalUserId, ct);

    protected override async Task<ProvisionResult> ProvisionNewUserAsync(
        ProvisionContext context, CancellationToken ct) {
        // Try invitation first
        var invited = await invitations.TryClaimAsync(context.Email, context.ExternalUserId, ct);
        if (invited is not null) {
            return ProvisionResult.Allow(invited.Roles);
        }

        // Fall through to self-service with default role
        var user = await this.CreateDefaultUserAsync(context, ct);
        return user is not null
            ? ProvisionResult.Allow(user.Roles)
            : ProvisionResult.Deny();
    }

    // ...
}

Registering provisioners against instances

Via the Runtime Extensions layer (Cirreum.Runtime.Identity or per-protocol packages):

// Program.cs
builder.AddIdentity()
    .AddProvisioner<ClientABorrowerProvisioner>("clientA_descope")
    .AddProvisioner<ClientBBorrowerProvisioner>("clientB_descope")
    .AddProvisioner<WorkforceProvisioner>("entraExternalId_primary");

var app = builder.Build();
app.MapIdentity();

Each AddProvisioner<T>(instanceKey) registers T as a keyed IUserProvisioner scoped service under instanceKey. At callback time the infra handler resolves the keyed provisioner whose key matches ProvisionContext.Source. Startup validation confirms every enabled instance in configuration has a registered provisioner.

Implementing a new identity-provider integration

Identity-provider implementations are separate Infrastructure packages (e.g. Cirreum.Identity.Oidc, Cirreum.Identity.EntraExternalId) that inherit IdentityProviderRegistrar<TSettings, TInstanceSettings>:

using Cirreum.Identity;
using Cirreum.Identity.Configuration;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

public sealed class MyIdpIdentityProviderRegistrar
    : IdentityProviderRegistrar<MyIdpSettings, MyIdpInstanceSettings> {

    public override string ProviderName => "MyIdp";

    protected override void RegisterProvisioner(
        string key,
        MyIdpInstanceSettings settings,
        IServiceCollection services,
        IConfiguration configuration) {
        // Bind settings, register provider-specific collaborators
        // (request validators, shared-secret verifiers, etc.).
        // The app's IUserProvisioner is registered separately as a keyed
        // service via AddProvisioner<T>(key) in the Runtime Extensions layer.
    }

    protected override void MapProvisioner(
        string key,
        MyIdpInstanceSettings settings,
        IEndpointRouteBuilder endpoints) {
        endpoints.MapPost(settings.Route, async (HttpContext ctx /*, ...*/) => {
            // Parse inbound request, authenticate it, build ProvisionContext,
            // resolve the keyed IUserProvisioner by Source, invoke ProvisionAsync,
            // translate the ProvisionResult into the IdP's expected response format.
        });
    }
}

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

Follows Semantic Versioning. Given its foundational role, major bumps are rare and carefully considered.

License

MIT — see LICENSE.


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

Showing the top 3 NuGet packages that depend on Cirreum.IdentityProvider:

Package Downloads
Cirreum.Identity.EntraExternalId

Microsoft Entra External ID integration for Cirreum — custom authentication extension (onTokenIssuanceStart) endpoint that validates Entra-signed tokens and provisions users before token issuance.

Cirreum.Runtime.IdentityProvider

The Cirreum Identity Provider for the Cirreum Runtime Server — config-driven registration helper that bootstraps any IdentityProviderRegistrar from Cirreum:Identity:Providers:{ProviderName}.

Cirreum.Identity.Oidc

OIDC identity provider integration for Cirreum — webhook-style pre-token provisioning callback compatible with Descope, Auth0, and any OIDC IdP that supports a custom-claims HTTP callback.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.5 101 6/5/2026
1.0.4 184 5/7/2026
1.0.3 173 5/1/2026
1.0.2 99 5/1/2026
1.0.1 253 4/24/2026
1.0.0 98 4/24/2026