ServiceCollectionExtensions Class
Wires the package into a host's service collection. Every registration lets a host pre-registration win: the extension supplies defaults, never overrides.
public static class ServiceCollectionExtensionsInheritance System.Object → ServiceCollectionExtensions
Methods
ServiceCollectionExtensions.AddBackChannelLogoutReceiver(this IServiceCollection, BackChannelLogoutValidationOptions) Method
Registers the receiver of Logout Tokens a provider posts to this application (OpenID Connect Back-Channel Logout 1.0 Section 2.6), as its own named validation profile.
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddBackChannelLogoutReceiver(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Abblix.SecurityEvents.BackChannelLogout.BackChannelLogoutValidationOptions options);Parameters
services Microsoft.Extensions.DependencyInjection.IServiceCollection
The service collection.
options BackChannelLogoutValidationOptions
What this receiver expects of every Logout Token: the provider as the issuer and this application's client identifier as the audience. Registered as the shared instance, so a host pre-registering its own wins.
Returns
Microsoft.Extensions.DependencyInjection.IServiceCollection
Remarks
A Logout Token is a security event token whose profile contradicts the security-event
default on two points, which is what a named profile is for: the default forbids exp
where Section 2.6 requires it, and pins the SET's own type where Section 4.1 forbids
requiring any. Both departures go through the reasoned allowance door, so a host reading
its boot log sees which critical defaults this profile does not carry and why.
It sits in this package rather than beside Shared Signals because a logout notification has no stream: one token, delivered once, from a provider the application already knows. What it uses is the token and the pipeline, both of which are here.
Registering this is the whole opt-in: an application that does not call it has nothing that accepts a Logout Token. The host still owes two registrations of its own: an ILogoutNotificationSink, because Section 2.7 makes locating and clearing the sessions the RP's and only the RP knows where it keeps them, and key resolution (for example AddJwksKeyResolution(this IServiceCollection, Action<JwksKeyResolutionOptions>)), because key trust is deployment knowledge. The request and the response themselves are this package's: BackChannelLogoutHandler reads the posted form and shapes the answer, leaving a host adapter nothing to decide but how to render it.
Step 8, the replay check, is optional in the specification and taken up here, because the
request carrying the token is unauthenticated and the token is a bearer credential in the
plainest sense. The default cache rides the host's IDistributedCache; a deployment
wanting a strictly atomic reservation derives a ReplayCacheBase over its own
store's conditional write and registers that, which TryAdd here then leaves alone.
ServiceCollectionExtensions.AddDiscoveryKeyResolution(this IServiceCollection, Action<JwksKeyResolutionOptions>) Method
Registers key resolution that asks each issuer where its keys are, reading "jwks_uri" from that issuer's discovery document instead of pinning an address at composition time.
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddDiscoveryKeyResolution(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Abblix.SecurityEvents.Infrastructure.JwksKeyResolutionOptions>? configure=null);Parameters
services Microsoft.Extensions.DependencyInjection.IServiceCollection
The service collection.
configure System.Action<JwksKeyResolutionOptions>
Cache lifetimes and any issuer-specific overrides, as usual.
Returns
Microsoft.Extensions.DependencyInjection.IServiceCollection
Remarks
Why an address pinned at composition time is worth removing: UseDiscoveryDocument.
A named entry and any selector answer first, so a host that knows better about one issuer keeps saying so; discovery stands where the well-known guess otherwise stands.
ServiceCollectionExtensions.AddDistributedReplayCache(this IServiceCollection) Method
Registers the replay cache over the host's IDistributedCache as the
IReplayCache.
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddDistributedReplayCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection services);Parameters
services Microsoft.Extensions.DependencyInjection.IServiceCollection
The service collection.
Returns
Microsoft.Extensions.DependencyInjection.IServiceCollection
Remarks
What it does depends on the path, and the name promises more than one of them delivers. Where a
caller offers a token to be TRUSTED - a DPoP proof, a client assertion, a logout token - the
reservation is read, and a second use of one identifier is refused. On push delivery it is only a
record of what this receiver accepted: PushDeliveryHandler writes after the sink and reads
nothing, because RFC 8935 Section 2 lets a transmitter redeliver and
ISecurityEventSink answers for that by requiring idempotent processing. Registering
this cache therefore makes deliveries auditable; it does not make a non-idempotent sink safe.
The store itself is the host's choice and is deliberately not registered here:
AddDistributedMemoryCache() gives a single-instance receiver process-local
behavior, Redis or another backend gives a scaled-out one a shared memory - the same
registration either way.
How long an identifier is remembered comes from the validation profile rather than from here, because the retention only makes sense against the freshness window it has to outlive - see ReplayRetention. The contract itself lives in Abblix.JWT, so a host that also runs the OpenID Connect server shares one replay store between its DPoP proofs, its client assertions and its events.
ServiceCollectionExtensions.AddJwksKeyResolution(this IServiceCollection, Action<JwksKeyResolutionOptions>) Method
Registers JWKS-based key resolution as the IIssuerKeyResolver: issuers' keys fetched from their published JWK Set documents and cached, with a forced refetch when a token names a key the cache has never seen.
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddJwksKeyResolution(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Abblix.SecurityEvents.Infrastructure.JwksKeyResolutionOptions>? configure=null);Parameters
services Microsoft.Extensions.DependencyInjection.IServiceCollection
The service collection.
configure System.Action<JwksKeyResolutionOptions>
Where key sets live and how long they answer from cache; a Shared Signals receiver sets the URI selector from the transmitter's advertised "jwks_uri".
Returns
Microsoft.Extensions.DependencyInjection.IServiceCollection
ServiceCollectionExtensions.AddSecurityEvents(this IServiceCollection, Action<SecurityEventsOptions>) Method
Registers the security-event core: the event registry and the default verifier and signer over the Abblix JWT core. Validation is NOT wired here - each consumer creates its own named profile with AddSecurityEventValidationProfile(this IServiceCollection, object, Action<ValidationProfile>).
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSecurityEvents(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Abblix.SecurityEvents.Infrastructure.SecurityEventsOptions>? configure=null);Parameters
services Microsoft.Extensions.DependencyInjection.IServiceCollection
The service collection.
configure System.Action<SecurityEventsOptions>
Configures the event dictionary and signing.
Returns
Microsoft.Extensions.DependencyInjection.IServiceCollection
Remarks
This call registers NO validator: validation lives in named profiles, each created by AddSecurityEventValidationProfile(this IServiceCollection, object, Action<ValidationProfile>) from the documented default steps and owned by the one consumer that names it. There is deliberately no unnamed shared family. It existed once, and it is the shape that produced the collision this API replaces: two consumers of security event tokens in one host shaped one family to contradictory demands, the outcome depended on registration order, and the loser saw every one of its tokens refused. An unnamed family invites exactly that consumer back - each editor believes the shared copy is its own - so the ceremony of naming a profile is the point, not a cost.
Two of the defaults ask for more configuration before they resolve, and each fails loudly naming what is missing: the verifier needs an IIssuerKeyResolver - key trust is deployment knowledge - and the signer needs SigningKeySource, which only a transmitter has. A pure receiver registers a resolver and never touches signing; a pure transmitter does the reverse.
ServiceCollectionExtensions.AddSecurityEventValidationProfile(this IServiceCollection, object, Action<ValidationProfile>) Method
Creates a NAMED validation profile: a keyed copy of the default step family that configure edits without touching any other profile, resolvable as a keyed ISecurityEventTokenValidator under profileKey.
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSecurityEventValidationProfile(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object profileKey, System.Action<Abblix.SecurityEvents.Infrastructure.ValidationProfile>? configure=null);Parameters
services Microsoft.Extensions.DependencyInjection.IServiceCollection
The service collection.
profileKey System.Object
The key the profile's validator resolves under.
configure System.Action<ValidationProfile>
Shapes the profile: step edits, critical declarations, allowances.
Returns
Microsoft.Extensions.DependencyInjection.IServiceCollection
Exceptions
System.InvalidOperationException
AddSecurityEvents(this IServiceCollection, Action<SecurityEventsOptions>) has not run, or a profile already exists under this key -
re-shaping an existing profile through a second registration would let two owners edit one
copy, which is the situation profiles exist to end.
Remarks
This exists for the host whose consumers contradict each other. One composed family per
host was enough until two token kinds met in one container: Back-Channel Logout REQUIRES
exp and pins typ to its own value, a Shared Signals SET forbids the former
and pins the latter differently - so whichever consumer edits the shared family breaks the
other, and the breakage surfaces as every token of the other kind being refused. A named
profile gives each consumer its own copy to shape - and no unnamed shared family exists
to collide over at all.
The copy is taken from the documented DEFAULTS, so a profile owner reasons from the baseline and no other consumer's decisions can reach it through registration order. Critical-step accounting is per profile for the same reason - the defaults' critical steps seed the profile, AddCriticalStep<TStep>() adds to it, and the guard judges each profile only by its own declarations and allowances.
ServiceCollectionExtensions.AddSecurityEventValidationProfileOnce(this IServiceCollection, string, Action<ValidationProfile>) Method
Creates a named validation profile, and only on first sight of its key.
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSecurityEventValidationProfileOnce(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string profileKey, System.Action<Abblix.SecurityEvents.Infrastructure.ValidationProfile> configure);Parameters
services Microsoft.Extensions.DependencyInjection.IServiceCollection
The service collection.
profileKey System.String
The key the profile's validator resolves under.
configure System.Action<ValidationProfile>
Shapes the profile: its steps, critical declarations, allowances.
Returns
Microsoft.Extensions.DependencyInjection.IServiceCollection
Remarks
A consumer's registration must survive being run twice without doubling its profile. What it must NOT survive is somebody else having taken the key first: that is the collision named profiles exist to end, and the loser sees every one of its tokens refused by a pipeline shaped for another kind.
Those two cases look identical from the key alone - a profile is there either way - so this leaves a marker of its own and reads that instead. A second call by the same registration finds its marker and does nothing; a foreign profile under the same key leaves no marker, so the strict registration runs and refuses loudly, naming the key.
ServiceCollectionExtensions.UseDefaultPipeline(this ValidationProfile) Method
Lays down the documented default pipeline, in its required order: parse, then the cheap unverified rejections, then the signature, then the checks that read trusted claims.
public static Abblix.SecurityEvents.Infrastructure.ValidationProfile UseDefaultPipeline(this Abblix.SecurityEvents.Infrastructure.ValidationProfile profile);Parameters
profile ValidationProfile
The profile being shaped.
Returns
Remarks
For a profile that wants the baseline and departs from it by editing - the shape most consumers of a plain SET want. A profile that judges a different KIND of token lists its own steps instead, because the departures are then the point rather than the exception, and a reader should not have to hold this order in mind to know what that profile does.