Extension Points Map: Which Interface Owns Which Behavior
You have wired AddOidcCore and the server runs. Now the real questions start: clients must live in your database, signing keys in your vault, consent in your UI, and one token-endpoint check has to follow your policy. Every one of those questions has the same shape — which of the public interfaces owns this behavior, and how do I make my implementation stick? — and this page maps the answers, seam by seam.
The architecture guide explains why the server is built this way; the Advanced DI guide covers the underlying DI toolkit in depth. This page stays at the level of to change X, replace Y.
Four rules cover every seam
Singular seams honor host pre-registration — your registration wins if it comes first. Register your implementation before AddOidcCore (or the feature's Add* call) and it takes the slot; the library's default never enters the container. The pattern is always the same:
builder.Services.AddSingleton<IClientInfoProvider, DbClientInfoProvider>();
builder.Services.AddOidcCore(options => { /* ... */ });
Match the default's lifetime: nearly every seam resolves as a singleton, so a database-backed implementation takes IServiceScopeFactory and opens a scope per call rather than injecting a scoped DbContext directly.
Pipeline families fold into composites — add members before composition, or edit the pipeline afterwards. Multi-member families (request-pipeline validators, client authenticators, logout notifiers, request fetchers, grant handlers) are collapsed into one composite at composition time: during AddOidcCore for the always-on families, or inside the opt-in call itself (AddDynamicClientRegistration, AddBackChannelAuthentication, AddDeviceAuthorization) for the families those bring in. A member registered after that point does not extend the pipeline — it replaces it: singular resolution returns only your implementation, every built-in step silently stops running, and nothing detects that at startup or runtime. So either register the member before the composing call, or take the pipeline apart with the Decompose<T>() cursor — AddFirst, AddAfter<TExisting>, Remove<TExisting>, Replace<TExisting> edit one step in one line. Grant handlers are the one family that instead fails loud with a clear exception when registered too late; for them, register-before is the only path. And not every *Validator is a family member: keyed validators such as IAuthorizationDetailValidator follow the keyed rule below.
Keyed seams dispatch on a wire discriminator — register under the key the protocol sends. Where the incoming message carries a discriminator (alg, subject_token_type, authorization_details.type, a JWS crit name), implementations are registered as keyed services under that exact string, and unknown keys are rejected at the protocol level. Keyed registrations follow the same first-wins rule as singular ones: register your implementation under a built-in key before the library's Add* call and it replaces the shipped default for that key.
Decorator seams wrap after registration — the default must already exist to be wrapped. Where the table says decorate, call services.Decorate<TService, YourDecorator>() after AddOidcCore: it wraps whatever is currently registered. Registering your own implementation before instead replaces the seam and silently drops the built-in decorators already stacked on it (the authorize-request processor, for example, ships wrapped in PAR and session-management decorators). The Advanced DI guide covers Decorate in detail.
The seams you must supply
Most seams have working defaults. Two do not:
IUserInfoProvider— the bridge to your user store. The library ships no default: connecting your user store (ASP.NET Identity, a custom table, an external directory) is the one integration every host writes. The Getting Started guide walks through it.IUserDeviceAuthenticationHandler— only if you enable CIBA. The shipped stub throws by design: how your users approve a backchannel login on their device (push notification, authenticator app) is inherently yours to decide.
And one near-mandatory seam: IAuthSessionService has a real default only when your host runs cookie authentication — the shipped adapter binds to the host's cookie authentication scheme. Hosts with a different session model implement this seam directly.
Clients and registration
| You need | Seam | Ships with | Notes |
|---|---|---|---|
| Clients in your database or config service | IClientInfoProvider | In-memory store over OidcOptions.Clients | About a hundred lines for a durable store; budget for caching — the provider sits on every request's hot path |
| Persist clients registered through DCR | IClientInfoManager | Same in-memory store | Pairs with the provider above |
| Custom client JWKS resolution or caching | IClientKeysProvider | Resolves keys from the client's registered JWKS or JWKS URI | |
| Your own dynamic-registration policy (allowed hosts, quotas, naming) | IClientRegistrationContextValidator | About twenty built-in steps | Composed family |
| Gate open registration with initial access tokens | IInitialAccessTokenService | Built-in issuance and validation | |
| client_id / client_secret format for DCR | IClientIdGenerator, IClientSecretGenerator | Secure random defaults |
Users, claims and consent
| You need | Seam | Ships with | Notes |
|---|---|---|---|
| Your user store behind the server | IUserInfoProvider | Nothing — you supply it | Mandatory for every deployment |
| Full control over claims assembly | IUserClaimsProvider | Orchestrates scopes, requested claims, subject conversion | |
| Custom scope-to-claims mapping | IScopeClaimsProvider | Standard OIDC scope mapping | |
| Dynamic or database-backed scope registry | IScopeManager | In-memory over OidcOptions.Scopes | |
| API resource registry (RFC 8707) | IResourceManager | In-memory over OidcOptions.Resources | |
| Persistent consent and a consent UI | IUserConsentsProvider | Auto-granting default | Replace to honor OIDC consent semantics; pending consents surface as a ConsentRequired outcome your host UI handles; prompt=consent handled by a decorator |
| Pairwise subject identifiers under your salt policy | ISubjectTypeConverter + AddPairwiseSubjectIdentifiers(...) | HMAC-SHA256 per OIDC Core §8.1 | |
| Custom session tracking (server-side sessions, SSO policy) | IAuthSessionService | Adapter over the host's cookie authentication | See The seams you must supply |
Tokens and grants
| You need | Seam | Ships with | Notes |
|---|---|---|---|
| Extra access-token claims, custom token shaping | IAccessTokenService | Signed JWT per RFC 9068 | |
| ID-token claim shaping | IIdentityTokenService | OIDC Core claims, nonce, hash-binding claims | |
| Refresh-token rotation and persistence policy | IRefreshTokenService | Rotation, absolute and sliding expiration | |
| A custom grant type | IAuthorizationGrantHandler via AddAuthorizationGrant<T>() | Code, refresh, client credentials, JWT bearer, token exchange; password / device / CIBA opt-in | Composed family; registers dispatch and discovery in one call; must precede AddOidcCore — fails loud otherwise |
| Custom token-endpoint validation step | ITokenContextValidator | Five built-in steps in load-bearing order | Composed family; worked example in the Advanced DI guide |
| Token and code lifetimes per client | Not a seam — ClientInfo properties | Per-client expirations with sensible defaults | Configuration, not code; for dynamic policy, shape tokens in IAccessTokenService |
| Audit events / SIEM hooks | Not a seam — no event bus ships | Structured log events with stable event ids | Decorate the service that owns the action, or consume the structured logs |
| Token exchange for new token types | ISubjectTokenResolver | JWT and refresh-token resolvers | Keyed by subject_token_type |
| Trusted issuer registry for the JWT bearer grant | IJwtBearerIssuerProvider | Config-driven registry with JWKS resolution | |
| Revocation-state storage | ITokenRegistry | Distributed-cache-backed status registry | |
| Replay-protection backend for assertions and proofs | IJwtReplayCache | Distributed-cache implementation | Use a shared cache (Redis) across instances |
Keys and cryptography
| You need | Seam | Ships with | Notes |
|---|---|---|---|
| Key rotation, vault- or database-held signing keys | IAuthServiceKeysProvider | Keys from OidcOptions certificates | Signing-key persistence guide; the provider returns key material to the signing pipeline — non-exportable HSM keys need the keyed signer seam below instead |
Support a JWS crit header extension | ICriticalHeaderHandler via AddCriticalHeaderHandler(name) | Unknown crit rejected per RFC 7515 | Keyed by header name |
| Swap or add a signing or key-management algorithm (HSM-backed RS256, an algorithm the library does not ship) | IDataSigner<TKey>, IKeyEncryptor<TKey> | RSA, ECDSA, HMAC signing; RSA, AES-GCM key wrap, direct key agreement | Keyed by JWS / JWE alg; each implementation self-declares its Algorithm, so registered algorithms appear in discovery automatically |
| Custom hashing for stored secrets | IHashService | SHA-based hashing |
Sessions and logout
| You need | Seam | Ships with | Notes |
|---|---|---|---|
Custom session_state computation or session store | ISessionManagementService | OIDC Session Management implementation | |
| Logout propagation beyond front/back-channel (message bus, custom protocols) | ILogoutNotifier | Front-channel and back-channel notifiers | Composed family |
| Custom delivery or retry of back-channel logout tokens | ILogoutTokenSender | HTTP delivery through SSRF-validated client | |
| Your own end-session request policy | IEndSessionContextValidator | Four built-in validation steps | Composed family |
Storage and operational state
| You need | Seam | Ships with | Notes |
|---|---|---|---|
| Operational state in your cache or database (codes, PAR, device and CIBA requests) | IEntityStorage | IDistributedCache-backed storage | Must keep get-and-remove atomic — single-use credentials depend on it |
| Key prefixes and namespacing in a shared cache | IEntityStorageKeyFactory | Standardized key layout | |
| Storage wire format (compatibility, migration) | IBinarySerializer | Protobuf with JSON fallback | |
| Custom authorization-code persistence | IAuthorizationCodeService | One-time codes over entity storage | |
| PAR request storage | IAuthorizationRequestStorage | Entity-storage-backed | |
| PKCE / nonce reuse-detection backend | IAuthorizationValueReuseDetector | Off until an interval is configured in OidcOptions | RFC 9700 hardening |
Authorization pipeline and flows
| You need | Seam | Ships with | Notes |
|---|---|---|---|
| A custom client-authentication method | IClientAuthenticator | none, client_secret_post/client_secret_basic, client_secret_jwt/private_key_jwt, mTLS variants | Composed family; each authenticator self-selects by credential shape |
| Custom authorize-request policy step | IAuthorizationContextValidator | Eleven built-in steps in load-bearing order | Composed family |
| A new request-object transport | IAuthorizationRequestFetcher | PAR, request_uri, inline request object | Composed family |
| A custom response type, or enabling implicit / none | IAuthorizationResponseBuilder via AddAuthorizationResponseProcessor<T>() | Code by default; EnableImplicitFlow(), EnableNoneFlow() opt-ins | A response type absent from DI does not exist — default-off semantics |
| Cross-cutting behavior at authorize time | IAuthorizationRequestProcessor (decorate) | Core processor, already decorated for PAR and session management | Decorator seam — see the fourth rule above |
RAR: your authorization_details types | IAuthorizationDetailValidator via AddAuthorizationDetailValidator(type) | None — with no validators, RAR requests are rejected | Keyed by type; advertised in discovery automatically |
| DPoP proof-validation policy | IProofValidator | RFC 9449 validation | |
| SSRF policy for outbound fetches (request_uri, JWKS, notifications) | ISecureHttpFetcher, ISecureUriValidator | SSRF-validating client, DNS-free URI checks | |
| CIBA user approval on the device | IUserDeviceAuthenticationHandler | Stub that throws — you supply it | Mandatory for CIBA |
| CIBA acceptance policy | IBackChannelAuthenticationContextValidator | Nine built-in steps | Composed family |
| Device-flow user-code format and verification UX | IUserCodeGenerator, IUserCodeVerificationService | Configurable alphabet; verification service your /device page calls |
Discovery, metadata and transport
| You need | Seam | Ships with | Notes |
|---|---|---|---|
| Per-tenant or multi-host issuer | IIssuerProvider | Configured issuer, or derived from the request when unset | Issuer string only — derive the tenant from ambient request context; client, key and scope providers are not tenant-aware and need the same tenant resolution |
| Dynamic or per-tenant ACR advertisement (MFA levels) | IAcrMetadataProvider | Reads OidcOptions.Discovery.AcrValuesSupported | A static set needs no seam — configure the option |
| Control advertised scopes and claims | IScopesAndClaimsProvider | Aggregated from registered scopes | |
| The core's view of the incoming request (proxies, rewriting) | IRequestInfoProvider | Per-adapter HTTP implementation | Pairs with issuer strategy |
| Custom HTTP shaping of any endpoint response | I*ResponseFormatter, one per endpoint (e.g. ITokenResponseFormatter) | Defaults per endpoint in both adapters | Decorate or replace per endpoint — see the fourth rule above |
| Endpoint paths | OidcRouteOptions (Minimal API) / tokenized route templates from configuration (MVC) | /connect/*, /.well-known/* fallbacks | Dynamic routes guide |
When the map is not enough
The seams above are the curated set, not the full inventory — the library's public surface is larger, and it is one file away: every default registration lives in the ServiceCollectionExtensions of its feature area in the source. If you still find no seam for the behavior you need, open an issue: the extension point you need may already exist under a name this page omits, or it will become a candidate for the next release.