Table of Contents

Class DataProtectingPersistedGrantStore

Namespace
Fdyay.IdentityServer.Stores
Assembly
Fdyay.IdentityServer.Core.dll

Protects a persisted grant's Data on the way into the store and back out of it.

public sealed class DataProtectingPersistedGrantStore : IPersistedGrantStore
Inheritance
DataProtectingPersistedGrantStore
Implements
Inherited Members

Remarks

A decorator over the grant store rather than a change to the four stores that write grants, and the reason is the one this tree keeps paying for. Consent, authorization codes, reference tokens and refresh tokens each serialize into Data; the device-flow store writes grants too. Protecting at each of them is five edits and a sixth store away from being wrong again — note 11 with five halves. Here there is one place, it sits exactly where the value crosses into storage, and a store added later inherits it by existing.

Only Data is touched. Key is what a lookup is by, and SubjectId, ClientId, SessionId, Type and the timestamps are what PersistedGrantFilter queries on — protecting any of them would make the store unable to find its own rows. That is a real limit and worth stating rather than implying: this protects the contents of a grant, not the fact that a subject has one.

Reading never consults the option that governs writing, which is what makes an upgrade safe in both directions; see IGrantDataProtector.

Constructors

DataProtectingPersistedGrantStore(IPersistedGrantStore, IGrantDataProtector)

Protection in front of inner.

public DataProtectingPersistedGrantStore(IPersistedGrantStore inner, IGrantDataProtector protector)

Parameters

inner IPersistedGrantStore

The store that holds the grants, resolved by InnerStoreKey.

protector IGrantDataProtector

What protects Data on write and unprotects it on read.

Fields

InnerStoreKey

The key the store being wrapped is registered under.

public const string InnerStoreKey = "Fdyay.IdentityServer.PersistedGrantStore.Unprotected"

Field Value

string

Remarks

A key rather than the plain service type, because the wrapper is registered as that service type itself. ProtectPersistedGrantData() moves whatever was registered here, so the container still owns the inner store's lifetime and its disposal.

Methods

GetAllAsync(PersistedGrantFilter, CancellationToken)

Gets all grants matching the filter.

public Task<IEnumerable<PersistedGrant>> GetAllAsync(PersistedGrantFilter filter, CancellationToken cancellationToken = default)

Parameters

filter PersistedGrantFilter
cancellationToken CancellationToken

Returns

Task<IEnumerable<PersistedGrant>>

GetAsync(string, CancellationToken)

Gets a grant by key, or null if not found.

public Task<PersistedGrant?> GetAsync(string key, CancellationToken cancellationToken = default)

Parameters

key string
cancellationToken CancellationToken

Returns

Task<PersistedGrant>

RemoveAllAsync(PersistedGrantFilter, CancellationToken)

Removes all grants matching the filter.

public Task RemoveAllAsync(PersistedGrantFilter filter, CancellationToken cancellationToken = default)

Parameters

filter PersistedGrantFilter
cancellationToken CancellationToken

Returns

Task

RemoveAsync(string, CancellationToken)

Removes a grant by key.

public Task RemoveAsync(string key, CancellationToken cancellationToken = default)

Parameters

key string
cancellationToken CancellationToken

Returns

Task

StoreAsync(PersistedGrant, CancellationToken)

Stores a grant.

public Task StoreAsync(PersistedGrant grant, CancellationToken cancellationToken = default)

Parameters

grant PersistedGrant
cancellationToken CancellationToken

Returns

Task

TryConsumeAsync(string, DateTime, CancellationToken)

Marks a grant consumed at consumedTime, unless it is gone or consumed already.

public Task<bool> TryConsumeAsync(string key, DateTime consumedTime, CancellationToken cancellationToken = default)

Parameters

key string

The grant's key.

consumedTime DateTime

When it was consumed, written to ConsumedTime.

cancellationToken CancellationToken

Cancels the write.

Returns

Task<bool>

True when this call consumed the grant; false when it was gone or another call had consumed it.

Remarks

The same guarantee as TryRemoveAsync(string, CancellationToken) for a one-time refresh token, which is consumed rather than removed so that a later presentation is recognised as a replay. The in-memory, Dapper and data-protecting stores consume in one step; this default reads, checks and writes back, as consuming did before 0.176.0.

TryRemoveAsync(string, CancellationToken)

Removes a grant by key, saying whether this call is the one that removed it.

public Task<bool> TryRemoveAsync(string key, CancellationToken cancellationToken = default)

Parameters

key string

The grant's key.

cancellationToken CancellationToken

Cancels the removal.

Returns

Task<bool>

True when the grant was there and this call removed it; false when it was already gone.

Remarks

What makes a one-time grant — an authorization code, a refresh token deleted on use — redeemable once when two requests present it at the same moment: both may read it, and only the one whose removal took it goes on. Ours (security), 0.176.0; Duende's store has no such member and its callers read, then remove.

The in-memory, Dapper and data-protecting stores take the grant in one step. This default, for a store of a host's own written before 0.176.0, reads and then removes — correct, and as open to the race as every store was before; a store that can delete and count the rows deleted in one statement should implement it.