Skip to content

RevocationCutoffChecker Class

Refuses a token issued before a revocation cutoff recorded against its subject or its session.

C#
public class RevocationCutoffChecker : Abblix.Oidc.Server.Features.Tokens.Revocation.IRevocationCutoffChecker

Inheritance System.Object → RevocationCutoffChecker

Implements IRevocationCutoffChecker

Remarks

Applies only to tokens this server minted. A cutoff names a principal in this server's own namespace, while the validator that consults this also sees tokens minted elsewhere - a client's private_key_jwt assertion (RFC 7523 Section 3, where sub is the client_id), an assertion from a federated issuer under the jwt-bearer grant, a request object, a software statement. Their subjects are strings from other namespaces, so matching one against our cutoff would refuse a stranger's token for a revocation that has nothing to do with it - and under client_credentials this server's own sub is a client_id too, so the two namespaces genuinely collide.

Measured against iat rather than auth_time. Both are OPTIONAL in general (RFC 7519 Section 4.1.6), but this server issues iat on every token it mints - RFC 9068 Section 2.2 makes it REQUIRED for the at+jwt profile its access tokens use - while auth_time is REQUIRED only when max_age was requested or it was asked for as an essential claim (OpenID Connect Core 1.0 Section 2). A check built on the second would pass silently for most tokens, which is worse than no check. A token arriving without iat is left alone: there is nothing to measure, and refusing it would revoke on the strength of a claim that was never there.

A cutoff therefore has two sides, and the token side alone would be a control that can be walked around: iat is stamped afresh by every authorization, so a browser session the revocation never reached would mint a replacement past the cutoff on the first attempt and on every attempt after. IsSessionRefusedAsync(AuthSession) is the other side, consulted wherever a session is about to be used: by the authorization endpoint before it reuses one, and by the token endpoint before it redeems a grant authorized from one.

The comparison is against the whole second the token declares. A JWT's iat is a whole number of seconds, so a token minted in the same second as a revocation reads as older than it and is refused. That errs towards refusing a token the revocation did not mean to catch, which is the direction to err in. Clock differences between instances run the other way and are not bounded by the token, which is what RevocationCutoffSkew answers - on this side only. The session side takes no tolerance, because there the same widening would refuse the fresh sign-in a user answers the refusal with, and the retry lands in the same window.

Constructors

RevocationCutoffChecker(ILogger<RevocationCutoffChecker>, IRevocationCutoffRegistry, IIssuerProvider, IOptions<OidcOptions>, IClientInfoProvider, ISubjectTypeConverter) Constructor

Refuses a token issued before a revocation cutoff recorded against its subject or its session.

C#
public RevocationCutoffChecker(Microsoft.Extensions.Logging.ILogger<Abblix.Oidc.Server.Features.Tokens.Revocation.RevocationCutoffChecker> logger, Abblix.Oidc.Server.Features.Storages.IRevocationCutoffRegistry cutoffRegistry, Abblix.Oidc.Server.Features.Issuer.IIssuerProvider issuerProvider, Microsoft.Extensions.Options.IOptions<Abblix.Oidc.Server.Common.Configuration.OidcOptions> options, Abblix.Oidc.Server.Features.ClientInformation.IClientInfoProvider clientInfoProvider, Abblix.Oidc.Server.Features.PairwiseIdentifiers.ISubjectTypeConverter subjectTypeConverter);

Parameters

logger Microsoft.Extensions.Logging.ILogger<RevocationCutoffChecker>

Records a refusal, so a revoked token is distinguishable from an expired one.

cutoffRegistry IRevocationCutoffRegistry

The registry of subject- and session-level revocation cutoffs.

issuerProvider IIssuerProvider

Names this server, so a cutoff is only measured against tokens it minted.

options Microsoft.Extensions.Options.IOptions<OidcOptions>

Carries the tolerance the comparison allows for clock differences.

clientInfoProvider IClientInfoProvider

Resolves the client a token names, so a pairwise pseudonym can be opened back into the subject a host would revoke.

subjectTypeConverter ISubjectTypeConverter

Opens that pseudonym.

Remarks

Applies only to tokens this server minted. A cutoff names a principal in this server's own namespace, while the validator that consults this also sees tokens minted elsewhere - a client's private_key_jwt assertion (RFC 7523 Section 3, where sub is the client_id), an assertion from a federated issuer under the jwt-bearer grant, a request object, a software statement. Their subjects are strings from other namespaces, so matching one against our cutoff would refuse a stranger's token for a revocation that has nothing to do with it - and under client_credentials this server's own sub is a client_id too, so the two namespaces genuinely collide.

Measured against iat rather than auth_time. Both are OPTIONAL in general (RFC 7519 Section 4.1.6), but this server issues iat on every token it mints - RFC 9068 Section 2.2 makes it REQUIRED for the at+jwt profile its access tokens use - while auth_time is REQUIRED only when max_age was requested or it was asked for as an essential claim (OpenID Connect Core 1.0 Section 2). A check built on the second would pass silently for most tokens, which is worse than no check. A token arriving without iat is left alone: there is nothing to measure, and refusing it would revoke on the strength of a claim that was never there.

A cutoff therefore has two sides, and the token side alone would be a control that can be walked around: iat is stamped afresh by every authorization, so a browser session the revocation never reached would mint a replacement past the cutoff on the first attempt and on every attempt after. IsSessionRefusedAsync(AuthSession) is the other side, consulted wherever a session is about to be used: by the authorization endpoint before it reuses one, and by the token endpoint before it redeems a grant authorized from one.

The comparison is against the whole second the token declares. A JWT's iat is a whole number of seconds, so a token minted in the same second as a revocation reads as older than it and is refused. That errs towards refusing a token the revocation did not mean to catch, which is the direction to err in. Clock differences between instances run the other way and are not bounded by the token, which is what RevocationCutoffSkew answers - on this side only. The session side takes no tolerance, because there the same widening would refuse the fresh sign-in a user answers the refusal with, and the retry lands in the same window.

Methods

RevocationCutoffChecker.CheckAsync(JsonWebTokenPayload) Method

The refusal a cutoff calls for on this token, or null when no cutoff reaches it.

C#
public System.Threading.Tasks.Task<Abblix.Jwt.JwtValidationError?> CheckAsync(Abblix.Jwt.JsonWebTokenPayload payload);

Parameters

payload JsonWebTokenPayload

The payload of a token that has already passed signature and lifetime checks.

Implements CheckAsync(JsonWebTokenPayload)

Returns

System.Threading.Tasks.Task<JwtValidationError>
A validation error when the token is refused, otherwise null.

RevocationCutoffChecker.IsSessionRefusedAsync(AuthSession) Method

Whether a cutoff refuses this authentication session, so nothing may mint against it.

C#
public System.Threading.Tasks.Task<bool> IsSessionRefusedAsync(Abblix.Oidc.Server.Features.UserAuthentication.AuthSession session);

Parameters

session AuthSession

A session about to be used: one the authorization endpoint is considering reusing, or the one a grant presented at the token endpoint was authorized from.

Implements IsSessionRefusedAsync(AuthSession)

Returns

System.Threading.Tasks.Task<System.Boolean>
true when the session must not be used.

Remarks

Without this the token side alone is half a control. A cutoff refuses tokens already issued, and iat is stamped afresh by every new authorization, so a browser session the revocation never touched can mint a replacement that clears the cutoff on the first try - and keeps doing so.

Measured against AuthenticationTime, not against a flag. A sign-in after the suspension is lifted produces a later authentication time and passes, which is the same property that lets a revoked subject sign in again with nothing to clean up. A boolean would refuse the fresh session too, and since the host's new session carries the same subject the request would loop back here for as long as the record is kept.