Table of Contents

Class DapperDeviceFlowStore

Namespace
Fdyay.IdentityServer.Dapper.Stores
Assembly
Fdyay.IdentityServer.Dapper.dll

Dapper-backed device flow store.

public sealed class DapperDeviceFlowStore : IDeviceFlowStore
Inheritance
DapperDeviceFlowStore
Implements
Inherited Members

Remarks

This is the store a multi-instance deployment needs. The device polls whichever instance the load balancer picks while the user approves on whichever instance their browser reached, so with the in-memory default a device can poll forever against an instance that never saw the approval.

The authorization data-protected since 0.177.0, as Duende's operational store serializes it through IPersistentGrantSerializer (measured): once approved it carries the user's subject, session and grant. A row written before is still read, through the protector's plaintext fallback. The codes stay columns, as Duende's do: the device and the user's browser look the row up by them.

Constructors

DapperDeviceFlowStore(IDbConnectionFactory, TimeProvider)

Creates a device flow store over the DeviceFlowCodes table.

public DapperDeviceFlowStore(IDbConnectionFactory connectionFactory, TimeProvider time)

Parameters

connectionFactory IDbConnectionFactory

Opens a connection for each operation and supplies the dialect's upsert.

time TimeProvider

The clock reads compare a row's expiry against.

Remarks

Writes each authorization unprotected, as before 0.177.0; the container builds the overload taking a protector.

DapperDeviceFlowStore(IDbConnectionFactory, TimeProvider, IGrantDataProtector)

Creates the store, protecting each authorization with protector.

public DapperDeviceFlowStore(IDbConnectionFactory connectionFactory, TimeProvider time, IGrantDataProtector protector)

Parameters

connectionFactory IDbConnectionFactory

Opens a connection for each operation and supplies the dialect's upsert.

time TimeProvider

The clock reads compare a row's expiry against.

protector IGrantDataProtector

Protects the Data column as it does a persisted grant's, under PersistentGrants.DataProtectData, and reads a row written unprotected as it was written.

Methods

FindByDeviceCodeAsync(string, CancellationToken)

The authorization stored under a device code, or null.

public Task<DeviceCode?> FindByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default)

Parameters

deviceCode string

The device code's key.

cancellationToken CancellationToken

Cancels the read.

Returns

Task<DeviceCode>

The authorization, or null when none is stored under that key.

Remarks

Reads the DeviceFlowCodes row whose DeviceCode matches. A miss returns null, and so does a row whose ExpiresAtUtc has passed, whether or not the sweep has removed it yet.

Exceptions

ArgumentException

deviceCode is null or empty.

FindByUserCodeAsync(string, CancellationToken)

The authorization stored under a user code, or null.

public Task<DeviceCode?> FindByUserCodeAsync(string userCode, CancellationToken cancellationToken = default)

Parameters

userCode string

The user code's key.

cancellationToken CancellationToken

Cancels the read.

Returns

Task<DeviceCode>

The authorization, or null when none is stored under that key.

Remarks

Reads the DeviceFlowCodes row whose UserCode matches. A miss returns null, and so does a row whose ExpiresAtUtc has passed, whether or not the sweep has removed it yet.

Exceptions

ArgumentException

userCode is null or empty.

RemoveByDeviceCodeAsync(string, CancellationToken)

Removes the authorization stored under a device code, under both of its keys.

public Task RemoveByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default)

Parameters

deviceCode string

The device code's key.

cancellationToken CancellationToken

Cancels the removal.

Returns

Task

Remarks

Deletes the DeviceFlowCodes row whose DeviceCode matches. A miss deletes nothing and is not an error.

Exceptions

ArgumentException

deviceCode is null or empty.

StoreDeviceAuthorizationAsync(string, string, DeviceCode, CancellationToken)

Stores a new pending authorization under both of its keys.

public Task StoreDeviceAuthorizationAsync(string deviceCode, string userCode, DeviceCode data, CancellationToken cancellationToken = default)

Parameters

deviceCode string

The device code's key — its hash, as IDeviceFlowCodeService passes it.

userCode string

The user code's key — its hash, as IDeviceFlowCodeService passes it.

data DeviceCode

The authorization.

cancellationToken CancellationToken

Cancels the write.

Returns

Task

Remarks

Upserts the DeviceFlowCodes row keyed by DeviceCode, with the user code, client id and an ExpiresAtUtc of the authorization's creation time plus its lifetime in seconds as columns and the authorization itself as JSON. A user code already held by another device code violates the unique index on UserCode and the provider's exception propagates.

Exceptions

ArgumentException

deviceCode or userCode is null or empty.

ArgumentNullException

data is null.

UpdateByUserCodeAsync(string, DeviceCode, CancellationToken)

Replaces the authorization stored under a user code, under both of its keys — a user's approval or denial.

public Task UpdateByUserCodeAsync(string userCode, DeviceCode data, CancellationToken cancellationToken = default)

Parameters

userCode string

The user code's key.

data DeviceCode

The authorization as it now stands.

cancellationToken CancellationToken

Cancels the write.

Returns

Task

Remarks

The in-tree stores do nothing when no authorization is stored under userCode.

Exceptions

ArgumentException

userCode is null or empty.

ArgumentNullException

data is null.