Skip to main content

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>() cursorAddFirst, 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 needSeamShips withNotes
Clients in your database or config serviceIClientInfoProviderIn-memory store over OidcOptions.ClientsAbout a hundred lines for a durable store; budget for caching — the provider sits on every request's hot path
Persist clients registered through DCRIClientInfoManagerSame in-memory storePairs with the provider above
Custom client JWKS resolution or cachingIClientKeysProviderResolves keys from the client's registered JWKS or JWKS URI
Your own dynamic-registration policy (allowed hosts, quotas, naming)IClientRegistrationContextValidatorAbout twenty built-in stepsComposed family
Gate open registration with initial access tokensIInitialAccessTokenServiceBuilt-in issuance and validation
client_id / client_secret format for DCRIClientIdGenerator, IClientSecretGeneratorSecure random defaults
You needSeamShips withNotes
Your user store behind the serverIUserInfoProviderNothing — you supply itMandatory for every deployment
Full control over claims assemblyIUserClaimsProviderOrchestrates scopes, requested claims, subject conversion
Custom scope-to-claims mappingIScopeClaimsProviderStandard OIDC scope mapping
Dynamic or database-backed scope registryIScopeManagerIn-memory over OidcOptions.Scopes
API resource registry (RFC 8707)IResourceManagerIn-memory over OidcOptions.Resources
Persistent consent and a consent UIIUserConsentsProviderAuto-granting defaultReplace 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 policyISubjectTypeConverter + AddPairwiseSubjectIdentifiers(...)HMAC-SHA256 per OIDC Core §8.1
Custom session tracking (server-side sessions, SSO policy)IAuthSessionServiceAdapter over the host's cookie authenticationSee The seams you must supply

Tokens and grants

You needSeamShips withNotes
Extra access-token claims, custom token shapingIAccessTokenServiceSigned JWT per RFC 9068
ID-token claim shapingIIdentityTokenServiceOIDC Core claims, nonce, hash-binding claims
Refresh-token rotation and persistence policyIRefreshTokenServiceRotation, absolute and sliding expiration
A custom grant typeIAuthorizationGrantHandler via AddAuthorizationGrant<T>()Code, refresh, client credentials, JWT bearer, token exchange; password / device / CIBA opt-inComposed family; registers dispatch and discovery in one call; must precede AddOidcCore — fails loud otherwise
Custom token-endpoint validation stepITokenContextValidatorFive built-in steps in load-bearing orderComposed family; worked example in the Advanced DI guide
Token and code lifetimes per clientNot a seam — ClientInfo propertiesPer-client expirations with sensible defaultsConfiguration, not code; for dynamic policy, shape tokens in IAccessTokenService
Audit events / SIEM hooksNot a seam — no event bus shipsStructured log events with stable event idsDecorate the service that owns the action, or consume the structured logs
Token exchange for new token typesISubjectTokenResolverJWT and refresh-token resolversKeyed by subject_token_type
Trusted issuer registry for the JWT bearer grantIJwtBearerIssuerProviderConfig-driven registry with JWKS resolution
Revocation-state storageITokenRegistryDistributed-cache-backed status registry
Replay-protection backend for assertions and proofsIJwtReplayCacheDistributed-cache implementationUse a shared cache (Redis) across instances

Keys and cryptography

You needSeamShips withNotes
Key rotation, vault- or database-held signing keysIAuthServiceKeysProviderKeys from OidcOptions certificatesSigning-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 extensionICriticalHeaderHandler via AddCriticalHeaderHandler(name)Unknown crit rejected per RFC 7515Keyed 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 agreementKeyed by JWS / JWE alg; each implementation self-declares its Algorithm, so registered algorithms appear in discovery automatically
Custom hashing for stored secretsIHashServiceSHA-based hashing

Sessions and logout

You needSeamShips withNotes
Custom session_state computation or session storeISessionManagementServiceOIDC Session Management implementation
Logout propagation beyond front/back-channel (message bus, custom protocols)ILogoutNotifierFront-channel and back-channel notifiersComposed family
Custom delivery or retry of back-channel logout tokensILogoutTokenSenderHTTP delivery through SSRF-validated client
Your own end-session request policyIEndSessionContextValidatorFour built-in validation stepsComposed family

Storage and operational state

You needSeamShips withNotes
Operational state in your cache or database (codes, PAR, device and CIBA requests)IEntityStorageIDistributedCache-backed storageMust keep get-and-remove atomic — single-use credentials depend on it
Key prefixes and namespacing in a shared cacheIEntityStorageKeyFactoryStandardized key layout
Storage wire format (compatibility, migration)IBinarySerializerProtobuf with JSON fallback
Custom authorization-code persistenceIAuthorizationCodeServiceOne-time codes over entity storage
PAR request storageIAuthorizationRequestStorageEntity-storage-backed
PKCE / nonce reuse-detection backendIAuthorizationValueReuseDetectorOff until an interval is configured in OidcOptionsRFC 9700 hardening

Authorization pipeline and flows

You needSeamShips withNotes
A custom client-authentication methodIClientAuthenticatornone, client_secret_post/client_secret_basic, client_secret_jwt/private_key_jwt, mTLS variantsComposed family; each authenticator self-selects by credential shape
Custom authorize-request policy stepIAuthorizationContextValidatorEleven built-in steps in load-bearing orderComposed family
A new request-object transportIAuthorizationRequestFetcherPAR, request_uri, inline request objectComposed family
A custom response type, or enabling implicit / noneIAuthorizationResponseBuilder via AddAuthorizationResponseProcessor<T>()Code by default; EnableImplicitFlow(), EnableNoneFlow() opt-insA response type absent from DI does not exist — default-off semantics
Cross-cutting behavior at authorize timeIAuthorizationRequestProcessor (decorate)Core processor, already decorated for PAR and session managementDecorator seam — see the fourth rule above
RAR: your authorization_details typesIAuthorizationDetailValidator via AddAuthorizationDetailValidator(type)None — with no validators, RAR requests are rejectedKeyed by type; advertised in discovery automatically
DPoP proof-validation policyIProofValidatorRFC 9449 validation
SSRF policy for outbound fetches (request_uri, JWKS, notifications)ISecureHttpFetcher, ISecureUriValidatorSSRF-validating client, DNS-free URI checks
CIBA user approval on the deviceIUserDeviceAuthenticationHandlerStub that throws — you supply itMandatory for CIBA
CIBA acceptance policyIBackChannelAuthenticationContextValidatorNine built-in stepsComposed family
Device-flow user-code format and verification UXIUserCodeGenerator, IUserCodeVerificationServiceConfigurable alphabet; verification service your /device page calls

Discovery, metadata and transport

You needSeamShips withNotes
Per-tenant or multi-host issuerIIssuerProviderConfigured issuer, or derived from the request when unsetIssuer 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)IAcrMetadataProviderReads OidcOptions.Discovery.AcrValuesSupportedA static set needs no seam — configure the option
Control advertised scopes and claimsIScopesAndClaimsProviderAggregated from registered scopes
The core's view of the incoming request (proxies, rewriting)IRequestInfoProviderPer-adapter HTTP implementationPairs with issuer strategy
Custom HTTP shaping of any endpoint responseI*ResponseFormatter, one per endpoint (e.g. ITokenResponseFormatter)Defaults per endpoint in both adaptersDecorate or replace per endpoint — see the fourth rule above
Endpoint pathsOidcRouteOptions (Minimal API) / tokenized route templates from configuration (MVC)/connect/*, /.well-known/* fallbacksDynamic 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.