Skip to content

ClockSkew Struct

How far a token's timestamps may sit either side of this clock and still be honoured.

C#
public readonly record struct ClockSkew : System.IEquatable<Abblix.Jwt.ClockSkew>

Implements System.IEquatable<ClockSkew>

Remarks

The two directions are separate numbers because only one of them is governed by anything. FAPI 2.0 Security Profile section 5.3.2.1 speaks exclusively of iat and nbf "in the future"; exp appears nowhere in that section, so how long a token stays usable past its stated end is this library's question to answer and a profile's to leave alone.

They travel as one type so that a caller passing a tolerance passes both halves or neither. A value carrying one half is indistinguishable from a deliberate asymmetry, which is what the profile below expresses on purpose.

Fields

ClockSkew.Fapi2 Field

What FAPI 2.0 Security Profile section 5.3.2.1 asks a server to accept: an iat or nbf "between 0 and 10 seconds in the future".

C#
public static readonly ClockSkew Fapi2;

Field Value

ClockSkew

Remarks

The asymmetry is the specification's, not a choice made here. That sentence speaks only of the future direction, so nothing in it extends the life of a token past the exp its own issuer chose - which is a deadline this server has no reason to move.

ClockSkew.Fapi2Ceiling Field

The furthest anything may be dated under FAPI 2.0 Security Profile section 5.3.2.1, which requires a server to "reject JWTs with an iat or nbf timestamp greater than 60 seconds in the future".

C#
public static readonly TimeSpan Fapi2Ceiling;

Field Value

System.TimeSpan

Remarks

A bound on whatever a caller asks for, which is why it is separate from Fapi2 rather than folded into it. Note 3 of that section says the number is in the document "to prevent implementations switching off iat and nbf checks completely", so it belongs to the profile: a deployment outside one answers to RFC 7523 Section 3, which names no bound at all.

The quoted sentence governs the forward direction; holding the backward one to the same number is this server's decision rather than the specification's. A profile distrusting a clock past some point one way has no reason to trust it further the other, and the alternative is a ceiling that reads as a bound on the tolerance while leaving half of it unbounded.

ClockSkew.None Field

No tolerance in either direction, and what a caller gets by saying nothing.

C#
public static readonly ClockSkew None;

Field Value

ClockSkew

Remarks

A default that granted time would widen an expiry check for every caller that never asked - including one enforcing a deadline of its own, which is what most callers of this type are doing. A tolerance is granted deliberately or not at all: a deployment-wide answer belongs to the security profile a host opts into, and a number a profile requires is the profile's to supply.

Properties

ClockSkew.Future Property

How far into the future a timestamp may reach and still be honoured: how far off a token's nbf or iat may be dated. This is the direction FAPI 2.0 section 5.3.2.1 speaks of, and the only one any specification here bounds.

C#
public System.TimeSpan Future { get; init; }

Property Value

System.TimeSpan

ClockSkew.Past Property

How far into the past a timestamp may reach and still be honoured: how long a token stays usable after the exp it carries.

C#
public System.TimeSpan Past { get; init; }

Property Value

System.TimeSpan

Methods

ClockSkew.BoundedBy(Nullable<TimeSpan>) Method

This tolerance with neither direction exceeding ceiling, or unchanged where there is no ceiling to hold it to.

C#
public Abblix.Jwt.ClockSkew BoundedBy(System.Nullable<System.TimeSpan> ceiling);

Parameters

ceiling System.Nullable<System.TimeSpan>

The furthest either direction may reach, or null for no bound.

Returns

ClockSkew

ClockSkew.WhyRefused(DateTimeOffset, Nullable<DateTimeOffset>, Nullable<DateTimeOffset>, Nullable<DateTimeOffset>) Method

Why a token's timestamps are refused at now, or null where this tolerance admits them.

C#
public string? WhyRefused(System.DateTimeOffset now, System.Nullable<System.DateTimeOffset> notBefore, System.Nullable<System.DateTimeOffset> expiresAt, System.Nullable<System.DateTimeOffset> issuedAt);

Parameters

now System.DateTimeOffset

The instant the timestamps are judged against.

notBefore System.Nullable<System.DateTimeOffset>

When the token says it starts, if it says.

expiresAt System.Nullable<System.DateTimeOffset>

When the token says it ends, if it says.

issuedAt System.Nullable<System.DateTimeOffset>

When the token says it was minted, if it says.

Returns

System.String

Remarks

The comparison belongs here rather than to whoever validates, because more than one caller asks it: a token is checked once against the tolerance it arrived under, and again wherever a tighter one turns out to apply. Two copies would part company on the boundaries, which is where they are least likely to be noticed - expiry is compared with <=, so a token exactly its whole tolerance past the end is already expired, while one exactly the whole tolerance ahead is still accepted.

The order is deliberate: a token both post-dated and expired answers "not yet valid", which is what its sender meant to send and what tells them so.

Operators

ClockSkew.implicit operator ClockSkew(TimeSpan) Operator

One number means the same tolerance either way, so a caller holding a single window assigns it directly.

C#
public static Abblix.Jwt.ClockSkew implicit operator Abblix.Jwt.ClockSkew(System.TimeSpan symmetric);

Parameters

symmetric System.TimeSpan

The tolerance to apply either way.

Returns

ClockSkew

Remarks

Implicit because the conversion loses nothing and cannot fail: a caller with one number has said what both halves are. The asymmetry belongs to a profile that prescribes one, never to a value somebody typed - a host naming one window means it in each direction, and having to spell that out is the sort of ceremony that gets one half set and the other forgotten.