IJwtReplayCache Interface
Abblix.Oidc.Server
Abblix.Oidc.Server.Features.ReplayPrevention
IJwtReplayCache Interface
Tracks JWT IDs (jti claims) presented to the server, so a JWT-bearing flow can detect replay attempts. Both RFC 7523 §5.2 (JWT-bearer-grant assertion replay) and RFC 9449 §11.1 (DPoP proof replay) want this primitive; it is intentionally namespace-neutral so a single distributed-cache instance serves every consumer.
public interface IJwtReplayCache
Derived
↳ DistributedJwtReplayCache
Remarks
Implementations should use distributed storage (e.g., Redis) so multi-instance deployments share the replay-protection state.
Methods
IJwtReplayCache.TryAddAsync(string, Nullable<DateTimeOffset>) Method
Records a fresh jti, returning true only on the first call for that
value. The single-call shape is atomic-by-contract: implementations are
expected to use the backend's compare-and-set primitive so concurrent
presenters of the same jti cannot both observe a miss and both succeed.
System.Threading.Tasks.Task<bool> TryAddAsync(string jti, System.Nullable<System.DateTimeOffset> expiresAt);
Parameters
jti System.String
The JWT ID (jti claim) to record.
expiresAt System.Nullable<System.DateTimeOffset>
Latest moment a same-jti replay could still pass the iat-window check; the
cache entry only needs to persist that long. null defers to the
implementation's default TTL.
Returns
System.Threading.Tasks.Task<System.Boolean>
true if the jti was newly recorded (proof is fresh); false if
it was already present (replay detected).
Remarks
Atomic-capable backends close the race natively: Redis SET … NX EX
(via StackExchange.Redis), SQL INSERT … ON CONFLICT DO NOTHING,
Memcached add, in-memory ConcurrentDictionary.TryAdd.
The default implementation DistributedJwtReplayCache uses Microsoft.Extensions.Caching.Distributed.IDistributedCache, which exposes only Get + Set and no compare-and-set primitive. It therefore provides only a probabilistic guarantee: two concurrent presenters of the same jti can both observe a miss before either writes. The race window is bounded by the cache round-trip and RFC 9449 §11.1 accepts probabilistic replay defence — but hosts that need strict atomicity should override the registration with a backend-aware implementation that bypasses Microsoft.Extensions.Caching.Distributed.IDistributedCache and talks to the chosen backend's atomic primitive directly.