SecurityProfileRequirements Class
The bundle of controls a ClientSecurityProfile forces on a client, expressed as individual flags the request-pipeline validators consult. This is the single place the profile-to-controls mapping lives, so a validator never needs to know what "FAPI 2.0" means - it only reads the one flag it owns - and adding a future profile touches only Resolve(ClientSecurityProfile).
public sealed record SecurityProfileRequirements : System.IEquatable<Abblix.Oidc.Server.Features.ClientInformation.SecurityProfileRequirements>Inheritance System.Object → SecurityProfileRequirements
Implements System.IEquatable<SecurityProfileRequirements>
Remarks
A flag normally requires a control and never relaxes one, so a profile tightens a client and
cannot weaken it. That is what lets a granular toggle (for example
PkceRequired set to false) coexist with a profile without silently
downgrading it.
One flag goes the other way, and the exception is deliberate rather than an escape hatch. ForbidRefreshTokenRotation removes a control, because the specification it comes from replaces that control with two others instead of dropping protection: rotation earns nothing once the client is confidential and its tokens are bound to their sender, and it costs a user their session whenever a client fails to store the token it was handed. A relaxing flag is therefore admissible only when the same profile carries the controls that stand in for what it removes, which FindUnreplacedRelaxations() checks for every profile at startup rather than leaving to review.
Every flag below names the validator that enforces it. That coupling is documented here on purpose: the enforcement is distributed across the request pipeline, so a new flag added to a profile without a matching consumer would ship silently unenforced. When adding a flag, wire a validator that reads it and a test that proves the control fires.
Properties
SecurityProfileRequirements.DefaultClockSkew Property
The tolerance in force where a deployment names none of its own - an answer this profile supplies rather than imposes: a deployment setting a value of its own wins over it, and is held only by MaxClockSkew.
public Abblix.Jwt.ClockSkew DefaultClockSkew { get; init; }Property Value
Remarks
Selecting no profile is a posture rather than the absence of one, and its answer is Abblix.Oidc.Server.Features.ClientInformation.SecurityProfileRequirements.UnprofiledClockSkew: an assertion arrives from an issuer whose clock this server does not run, and RFC 7523 Section 3 allows for that offset without naming a bound.
Where a profile bounds freshness the two halves part company: an expiry the client itself chose is a deadline this server has no reason to extend, because the grace exists for a clock that disagrees rather than for a token that is simply late.
SecurityProfileRequirements.ForbidRefreshTokenRotation Property
The profile forbids refresh token rotation, which is the one flag that removes a control
rather than requiring one. See the remarks on this type for why that is admissible here and
what stands in its place. Enforced by
Features.Tokens.RefreshTokenService.
public bool ForbidRefreshTokenRotation { get; init; }Property Value
SecurityProfileRequirements.MaxClockSkew Property
The furthest either clock window may reach under this profile, or null where the profile puts no bound on them. Read by every place that builds JWT validation parameters.
public System.Nullable<System.TimeSpan> MaxClockSkew { get; init; }Property Value
System.Nullable<System.TimeSpan>
Remarks
Unlike the flags around it this carries a VALUE, because the requirement names one and a boolean would leave each reader to remember it - which is how two readers come to bound the same thing differently. Null rather than a large number, so that a profile putting no bound on the future is the ABSENCE of one and cannot be mistaken for a generous bound somebody chose.
SecurityProfileRequirements.RequireCodeResponseTypeOnly Property
The profile permits only the authorization-code response type, rejecting any implicit or
hybrid response type that returns a token or id_token from the authorization endpoint.
Enforced by Endpoints.Authorization.Validation.FlowTypeValidator at request time and
by SecurityProfileConsistency as a fail-loud registration/startup check.
public bool RequireCodeResponseTypeOnly { get; init; }Property Value
SecurityProfileRequirements.RequireConfidentialClient Property
The profile admits only confidential clients as defined by RFC 6749, so a client that authenticates with nothing at the token endpoint cannot be held to it. Enforced by SecurityProfileConsistency at registration and at startup.
public bool RequireConfidentialClient { get; init; }Property Value
SecurityProfileRequirements.RequireIssuerAudienceInClientAssertion Property
The profile accepts only the server's issuer identifier, and only as a string, in the
audience of a client authentication assertion, narrowing what the underlying specification
otherwise permits. Enforced by
Features.ClientAuthentication.JwtAssertionAuthenticatorBase.
public bool RequireIssuerAudienceInClientAssertion { get; init; }Property Value
SecurityProfileRequirements.RequireKeyBasedClientAuthentication Property
The profile admits only client authentication that proves possession of a key: mutual TLS (RFC 8705 section 2) or a private key JWT assertion (OpenID Connect Core section 9). Every method keyed on a shared secret is refused. Enforced by SecurityProfileConsistency at registration and at startup.
public bool RequireKeyBasedClientAuthentication { get; init; }Property Value
SecurityProfileRequirements.RequirePkce Property
The profile mandates PKCE on every authorization request, even when the client's own
PkceRequired is false. Enforced by
Endpoints.Authorization.Validation.PkceValidator.
public bool RequirePkce { get; init; }Property Value
SecurityProfileRequirements.RequirePushedAuthorizationRequests Property
The profile requires the client to start every authorization flow through a Pushed
Authorization Request, independent of the server-wide
RequirePushedAuthorizationRequests flag.
Enforced by Endpoints.Authorization.RequestFetching.PushedRequestFetcher.
public bool RequirePushedAuthorizationRequests { get; init; }Property Value
SecurityProfileRequirements.RequireS256CodeChallenge Property
The profile restricts the PKCE code challenge method to exactly S256, rejecting both
plain and the non-standard S512 extension. FAPI 2.0 names S256, and the
IANA "PKCE Code Challenge Methods" registry defines only plain and S256, so a
conformance suite never presents S512. Enforced by
Endpoints.Authorization.Validation.PkceValidator.
public bool RequireS256CodeChallenge { get; init; }Property Value
SecurityProfileRequirements.RequireSenderConstrainedTokens Property
The profile requires a sender-constrained access token, satisfied by either a DPoP proof
(RFC 9449) or a certificate-bound token over mutual TLS (RFC 8705 section 3). Enforced by
Endpoints.Token.Validation.DPoPTokenEndpointValidator.
public bool RequireSenderConstrainedTokens { get; init; }Property Value
SecurityProfileRequirements.RequireStrictRequestObjectProcessing Property
The profile requires strict RFC 9101 section 6.3 request-object processing: only the parameters inside the
request object are used and any parameter passed outside it is ignored, instead of the OpenID Connect
Core section 6.1 merge behaviour. FAPI 2.0 mandates JWT-Secured Authorization Requests with this exclusivity.
Enforced by Features.RequestObject.RequestObjectFetcher.
public bool RequireStrictRequestObjectProcessing { get; init; }Property Value
Methods
SecurityProfileRequirements.ClockSkewOrDefault(Nullable<ClockSkew>) Method
The tolerance that actually applies: what the caller configured where it configured anything, otherwise DefaultClockSkew, in either case held to MaxClockSkew.
public Abblix.Jwt.ClockSkew ClockSkewOrDefault(System.Nullable<Abblix.Jwt.ClockSkew> configured=null);Parameters
configured System.Nullable<ClockSkew>
A tolerance the caller set, or null to take this profile's own.
Returns
Remarks
The bound is applied HERE rather than carried onward beside the value, so that no reader can take one without the other. A ceiling travelling as a second field is a ceiling somebody forgets to pass, and the omission reads as a deployment allowed to be looser rather than as the mistake it is.
SecurityProfileRequirements.FindUnreplacedRelaxations() Method
Names every profile that removes a control without carrying the controls that stand in for it. An empty list means each relaxation in this file is paid for.
public static System.Collections.Generic.IReadOnlyList<string> FindUnreplacedRelaxations();Returns
System.Collections.Generic.IReadOnlyList<System.String>
Remarks
This exists because a relaxing flag is one edit away from becoming an ordinary permission. Someone adding a profile, or loosening an existing one, sees a set of booleans with no direction to them, and nothing in the type distinguishes the flag that removes protection from the nine that add it. So the condition that makes the removal sound is stated as code and run at startup, where it can fail, rather than as a paragraph that can be skipped.
Refusing refresh token rotation is sound only alongside a confidential client and a sender-constrained token, because those two are what make rotation redundant. A profile carrying the relaxation without them would hand out long-lived multi-use refresh tokens to a client that may be public and whose tokens anyone may replay.
SecurityProfileRequirements.For(ClientInfo, ClientSecurityProfile) Method
The control bundle a client is actually held to: what the deployment demands of everyone, tightened by whatever the client names for itself.
public static Abblix.Oidc.Server.Features.ClientInformation.SecurityProfileRequirements For(Abblix.Oidc.Server.Features.ClientInformation.ClientInfo client, Abblix.Oidc.Server.Common.Constants.ClientSecurityProfile defaultProfile);Parameters
client ClientInfo
The client whose effective profile is being resolved.
defaultProfile ClientSecurityProfile
The profile the deployment holds every client to.
Returns
Remarks
A deployment-wide profile is a FLOOR, not a default. Turning one on is a statement about every client the server serves, so a client cannot step out from under it - it can only ask for more. The alternative reading, where a client naming a profile replaces the deployment's, makes the server-wide setting a suggestion: one registration would quietly leave a FAPI 2.0 deployment serving a client under none of its controls, and nothing about that registration would look like a decision to weaken the server.
The combination is per CONTROL rather than a choice between two bundles, because "stricter" is not a property a bundle has - one profile can demand sender-constrained tokens while another names a tighter clock window, and picking either bundle whole would drop the other's demand.
SecurityProfileRequirements.Resolve(ClientSecurityProfile) Method
Returns the control bundle a given profile mandates.
public static Abblix.Oidc.Server.Features.ClientInformation.SecurityProfileRequirements Resolve(Abblix.Oidc.Server.Common.Constants.ClientSecurityProfile profile);Parameters
profile ClientSecurityProfile
Returns
Remarks
The default arm throws rather than answering Abblix.Oidc.Server.Features.ClientInformation.SecurityProfileRequirements.NoneRequirements. A profile added to the enum without a bundle here would otherwise resolve to no requirements at all, which is silently the weakest answer available and reads at every call site as a deliberate one.
A value the enum does not define is a different population and gets a different answer. It arrives from outside - a configuration binder takes a number outside the range as it stands, and a client store the host writes can hold anything - so it is data rather than a mistake in this file, and the readers meeting it are handling a live request. Throwing there turns a host's bad value into a 500 from the authorization and token endpoints, which is the reader's failure rather than the writer's. It resolves to Abblix.Oidc.Server.Features.ClientInformation.SecurityProfileRequirements.StrictestRequirements instead: nothing here can say what the value meant, and of the answers available only the strictest cannot quietly serve a client the deployment believed was constrained.