ClockSkew Struct
How far a token's timestamps may sit either side of this clock and still be honoured.
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".
public static readonly ClockSkew Fapi2;Field Value
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".
public static readonly TimeSpan Fapi2Ceiling;Field Value
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.
public static readonly ClockSkew None;Field Value
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.
public System.TimeSpan Future { get; init; }Property Value
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.
public System.TimeSpan Past { get; init; }Property Value
Methods
ClockSkew.BoundedBy(Nullable<TimeSpan>) Method
This tolerance with neither direction exceeding ceiling, or unchanged where there is no ceiling to hold it to.
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.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.
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
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.
public static Abblix.Jwt.ClockSkew implicit operator Abblix.Jwt.ClockSkew(System.TimeSpan symmetric);Parameters
symmetric System.TimeSpan
The tolerance to apply either way.
Returns
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.