#### [Abblix\.Oidc\.Server](https://www.abblix.com/docs/api/abblix-oidc-server 'index')
### [Abblix\.Oidc\.Server\.Features\.ReplayPrevention](https://www.abblix.com/docs/api/abblix-oidc-server/Abblix.Oidc.Server.Features.ReplayPrevention 'Abblix\.Oidc\.Server\.Features\.ReplayPrevention')

## DistributedJwtReplayCache Class

The deprecated contract's default implementation, kept so a host that resolves
[IJwtReplayCache](https://www.abblix.com/docs/api/abblix-oidc-server/Abblix.Oidc.Server.Features.ReplayPrevention.IJwtReplayCache 'Abblix\.Oidc\.Server\.Features\.ReplayPrevention\.IJwtReplayCache') still receives a working object\. It stores nothing of its own:
every reservation goes to the same [IReplayCache](https://www.abblix.com/docs/api/abblix-jwt/Abblix.Jwt.ReplayPrevention.IReplayCache 'Abblix\.Jwt\.ReplayPrevention\.IReplayCache') the server's own consumers use,
so the deprecated and current spellings share one set of entries and cannot disagree about
whether an identifier has been seen\.

```csharp
public class DistributedJwtReplayCache : Abblix.Oidc.Server.Features.ReplayPrevention.IJwtReplayCache
```

Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') → DistributedJwtReplayCache

Implements [IJwtReplayCache](https://www.abblix.com/docs/api/abblix-oidc-server/Abblix.Oidc.Server.Features.ReplayPrevention.IJwtReplayCache 'Abblix\.Oidc\.Server\.Features\.ReplayPrevention\.IJwtReplayCache')

### Remarks
The only behaviour left here is the one the current contract deliberately dropped: an absent
expiry\. The moved contract requires its caller to say when an identifier stops being worth
remembering, because a cache that guesses either outlives or forgets the window its caller
actually validates against\. This shim keeps guessing on its callers' behalf, with the hour the
deprecated contract names\.
### Constructors

## DistributedJwtReplayCache\(IReplayCache, TimeProvider\) Constructor {#Abblix.Oidc.Server.Features.ReplayPrevention.DistributedJwtReplayCache.DistributedJwtReplayCache(Abblix.Jwt.ReplayPrevention.IReplayCache,System.TimeProvider)}

The deprecated contract's default implementation, kept so a host that resolves
[IJwtReplayCache](https://www.abblix.com/docs/api/abblix-oidc-server/Abblix.Oidc.Server.Features.ReplayPrevention.IJwtReplayCache 'Abblix\.Oidc\.Server\.Features\.ReplayPrevention\.IJwtReplayCache') still receives a working object\. It stores nothing of its own:
every reservation goes to the same [IReplayCache](https://www.abblix.com/docs/api/abblix-jwt/Abblix.Jwt.ReplayPrevention.IReplayCache 'Abblix\.Jwt\.ReplayPrevention\.IReplayCache') the server's own consumers use,
so the deprecated and current spellings share one set of entries and cannot disagree about
whether an identifier has been seen\.

```csharp
public DistributedJwtReplayCache(Abblix.Jwt.ReplayPrevention.IReplayCache replayCache, System.TimeProvider timeProvider);
```
#### Parameters

###### `replayCache` [IReplayCache](https://www.abblix.com/docs/api/abblix-jwt/Abblix.Jwt.ReplayPrevention.IReplayCache 'Abblix\.Jwt\.ReplayPrevention\.IReplayCache') {#Abblix.Oidc.Server.Features.ReplayPrevention.DistributedJwtReplayCache.DistributedJwtReplayCache(Abblix.Jwt.ReplayPrevention.IReplayCache,System.TimeProvider).replayCache}

Where the reservation lands\.

###### `timeProvider` [System\.TimeProvider](https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider 'System\.TimeProvider') {#Abblix.Oidc.Server.Features.ReplayPrevention.DistributedJwtReplayCache.DistributedJwtReplayCache(Abblix.Jwt.ReplayPrevention.IReplayCache,System.TimeProvider).timeProvider}

Turns an absent expiry into an absolute moment\.

### Remarks
The only behaviour left here is the one the current contract deliberately dropped: an absent
expiry\. The moved contract requires its caller to say when an identifier stops being worth
remembering, because a cache that guesses either outlives or forgets the window its caller
actually validates against\. This shim keeps guessing on its callers' behalf, with the hour the
deprecated contract names\.
### Methods

## DistributedJwtReplayCache\.TryAddAsync\(string, Nullable\<DateTimeOffset\>\) Method {#Abblix.Oidc.Server.Features.ReplayPrevention.DistributedJwtReplayCache.TryAddAsync(string,System.Nullable_System.DateTimeOffset_)}

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\.

```csharp
public System.Threading.Tasks.Task<bool> TryAddAsync(string jti, System.Nullable<System.DateTimeOffset> expiresAt);
```
#### Parameters

###### `jti` [System\.String](https://learn.microsoft.com/en-us/dotnet/api/system.string 'System\.String') {#Abblix.Oidc.Server.Features.ReplayPrevention.DistributedJwtReplayCache.TryAddAsync(string,System.Nullable_System.DateTimeOffset_).jti}

The JWT ID \(jti claim\) to record\.

###### `expiresAt` [System\.Nullable&lt;](https://learn.microsoft.com/en-us/dotnet/api/system.nullable-1 'System\.Nullable\`1')[System\.DateTimeOffset](https://learn.microsoft.com/en-us/dotnet/api/system.datetimeoffset 'System\.DateTimeOffset')[&gt;](https://learn.microsoft.com/en-us/dotnet/api/system.nullable-1 'System\.Nullable\`1') {#Abblix.Oidc.Server.Features.ReplayPrevention.DistributedJwtReplayCache.TryAddAsync(string,System.Nullable_System.DateTimeOffset_).expiresAt}

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\.

Implements [TryAddAsync\(string, Nullable&lt;DateTimeOffset&gt;\)](https://www.abblix.com/docs/api/abblix-oidc-server/Abblix.Oidc.Server.Features.ReplayPrevention.IJwtReplayCache#Abblix.Oidc.Server.Features.ReplayPrevention.IJwtReplayCache.TryAddAsync(string,System.Nullable_System.DateTimeOffset_) 'Abblix\.Oidc\.Server\.Features\.ReplayPrevention\.IJwtReplayCache\.TryAddAsync\(string, System\.Nullable\<System\.DateTimeOffset\>\)')

#### Returns
[System\.Threading\.Tasks\.Task&lt;](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1 'System\.Threading\.Tasks\.Task\`1')[System\.Boolean](https://learn.microsoft.com/en-us/dotnet/api/system.boolean 'System\.Boolean')[&gt;](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1 'System\.Threading\.Tasks\.Task\`1')  
`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](https://www.abblix.com/docs/api/abblix-oidc-server/Abblix.Oidc.Server.Features.ReplayPrevention.DistributedJwtReplayCache 'Abblix\.Oidc\.Server\.Features\.ReplayPrevention\.DistributedJwtReplayCache') uses
[Microsoft\.Extensions\.Caching\.Distributed\.IDistributedCache](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.distributed.idistributedcache '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](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.distributed.idistributedcache 'Microsoft\.Extensions\.Caching\.Distributed\.IDistributedCache') and
talks to the chosen backend's atomic primitive directly.
