Duende IdentityServer Stores
When to Use This Skill
- You are wiring up
AddConfigurationStore()orAddOperationalStore()with EF Core and need correct registration, migration assembly setup, and schema configuration. - You are implementing a custom
IClientStore,IResourceStore,IPersistedGrantStore, orISigningKeyStoreagainst a non-EF data source (Redis, Mongo, external API, etc.). - You need to enable and tune configuration store caching (
AddConfigurationStoreCache(), expiration windows, distributed cache setup) to reduce database load. - You are managing EF Core migrations across IdentityServer versions and need to correctly handle schema drift for
ConfigurationDbContextandPersistedGrantDbContext. - You are enabling server-side sessions (
IServerSideSessionStore) and need to understand session lifecycle, cleanup, and storage integration. - You are troubleshooting stale client or resource data, expired token accumulation, or signing key rotation failures tied to store configuration.
- You are designing a multi-tenant IdentityServer deployment and need to choose between database-per-tenant and shared-database store strategies.
Core Principles
Store interfaces decouple IdentityServer from persistence. All data access goes through store interfaces registered in the ASP.NET Core DI container. IdentityServer does not care what database backs them — EF Core, Redis, MongoDB, or a static in-memory collection are all equally valid.
Two independent store categories exist: configuration and operational. They can be used independently or together. Configuration data is relatively static (clients, resources, CORS); operational data is dynamic and high-write (grants, sessions, signing keys). They should be sized, cached, and maintained with those distinct access patterns in mind.
Operational data is protected at rest. The Data payload of persisted grants and serialized signing keys is encrypted using the ASP.NET Core Data Protection API. Key rotation and Data Protection configuration must be coordinated — a lost Data Protection key makes stored grants and signing keys unreadable.
Consumed grants are soft-deleted, not immediately removed. One-time-use grants (e.g., authorization codes, one-time refresh tokens) are marked with a ConsumedTime rather than deleted. This enables threat detection in custom IRefreshTokenService implementations. Do not confuse consumed with expired — the token cleanup service only removes records past their Expiration, not consumed ones (unless RemoveConsumedTokens is enabled).
EF Core schema changes are your responsibility. Duende does not ship automatic migration scripts or schema upgrade tooling. You own migration creation, application, and data migration between IdentityServer versions.
Docs: https://docs.duendesoftware.com/identityserver/data
NuGet Package
This package provides EF Core implementations for all configuration and operational store interfaces.
Store Architecture
IdentityServer's data is split into two categories, each with its own set of store interfaces:
Configuration Data
Stores static, rarely-changing data that describes how IdentityServer behaves:
Operational Data
Stores dynamic, high-write runtime state that IdentityServer creates and manages during request processing:
EF Core Integration
The Duende.IdentityServer.EntityFramework NuGet package provides EF Core-backed implementations of all store interfaces. It ships two DbContext types:
ConfigurationDbContext— backsIClientStore,IResourceStore,ICorsPolicyService,IIdentityProviderStorePersistedGrantDbContext— backsIPersistedGrantStore,IDeviceFlowStore,ISigningKeyStore,IServerSideSessionStore
Registering Both Stores
Separate Schemas
Isolate configuration and operational tables using DefaultSchema to avoid naming collisions and simplify backup strategies:
Migrations
EF Core migrations must be created in the host assembly. IdentityServer does not generate or apply migrations automatically.
Creating Migrations
Applying Migrations at Startup
Handling Schema Updates Across Versions
When upgrading IdentityServer, always check the upgrade guide for schema changes before applying the new package version:
- Review the changelog for any new columns or tables in
ConfigurationDbContextorPersistedGrantDbContext. - Scaffold a new EF migration:
dotnet ef migrations add UpgradeToV7x --context ConfigurationDbContext. - Review the generated migration SQL — especially for columns with
NOT NULLconstraints that require backfill. - Apply to a staging environment and validate before production.
Caching Configuration Data
Configuration data (clients, resources, CORS) is read on every token request. Without caching, every request hits the database.
EF Store Caching (Recommended)
AddConfigurationStoreCache() wraps each configuration store with a caching decorator backed by Microsoft HybridCache. Cache expiration is controlled through IdentityServerOptions.Caching:
Custom Store Caching
When using a custom IClientStore, wrap it with the caching decorator explicitly:
Distributed Cache for Multi-Node Deployments
In-memory cache is node-local — a client update only invalidates the cache on the node where the change was made. For multi-node deployments, configure HybridCache with a distributed backend:
Note: In v8,
ICache<T>is replaced by MicrosoftHybridCache. If you have customICache<T>implementations, migrate toHybridCachewith keyed services (ServiceProviderKeys.ConfigurationStoreCache). See theidentityserver-upgrade-v7-to-v8skill for migration patterns.After a client or resource update, explicitly evict the cache entry or wait for expiration. There is no built-in cache invalidation webhook.
Custom Stores
Implement custom stores when EF Core is unsuitable — for example, when client definitions live in an external system, or when operational data must be stored in Redis or a document database.
In-Memory Stores (Development Only)
For development and testing, in-memory stores avoid database setup entirely:
In-memory stores are created once at startup and cannot be updated at runtime without restarting the application. They do not survive restarts and should never be used for operational data in production.
Version Note — CancellationToken parameters (v8+): The store interface signatures below include
CancellationTokenparameters, which were added in Duende IdentityServer v8. In v7 and earlier, these interfaces do not acceptCancellationToken— omit the parameter when targeting v7. Additionally,IClientStore.GetAllClientsAsyncis a new method in v8; it does not exist in v7.
IClientStore
IResourceStore
IPersistedGrantStore
Server-Side Sessions Store
Server-side sessions (added in IdentityServer 6.1) keep authentication session data server-side rather than in the cookie, enabling centralized session management, inactivity timeouts, and back-channel logout across all sessions for a user.
Enabling with EF Core
Custom IServerSideSessionStore
The IServerSideSessionStore interface provides methods for CreateSessionAsync, GetSessionAsync, UpdateSessionAsync, DeleteSessionAsync, and bulk query/management methods used by session expiration and back-channel logout coordination. All methods must be implemented — there are no default no-op implementations.
Session Cleanup
Session records accumulate over time. Token cleanup (EnableTokenCleanup) removes expired sessions from the EF operational store. For custom stores, you must implement your own cleanup background service.
Signing Key Store
Duende IdentityServer's automatic key management feature dynamically creates and rotates signing keys. Keys must be persisted across restarts and shared across nodes.
Default: File System
The default ISigningKeyStore persists keys to the file system. This is suitable for single-node deployments only:
EF Core Key Store
AddOperationalStore() automatically registers ISigningKeyStore against PersistedGrantDbContext:
Custom ISigningKeyStore
The ISigningKeyStore interface has three methods (CancellationToken parameters are v8+ only — see version note above):
LoadKeysAsync(CancellationToken ct)— returns allSerializedKeyrecords; called on startup and periodicallyStoreKeyAsync(SerializedKey key, CancellationToken ct)— persists a newly created keyDeleteKeyAsync(string id, CancellationToken ct)— removes a retired key
Data Protection Considerations
The Data property of SerializedKey may be encrypted via ASP.NET Core Data Protection (check DataProtected == true). When implementing a custom store:
- Do not re-encrypt data returned from
LoadKeysAsync— IdentityServer decrypts it internally. - Ensure Data Protection keys are shared across all nodes in a multi-node deployment. If node A encrypts a signing key and node B cannot decrypt it, token signing will fail.
- Store Data Protection keys in a shared location (Azure Blob, SQL, Redis) and protect them with a shared certificate or key vault key.
Token Cleanup
Operational data accumulates continuously. Without cleanup, the PersistedGrants table grows unbounded, degrading query performance.
Enabling Automatic Cleanup
OperationalStoreOptions Reference
What Token Cleanup Removes
The TokenCleanupService removes:
- Persisted grants where
Expiration < UtcNow - Consumed tokens when
RemoveConsumedTokens = trueandConsumedTime + ConsumedTokenCleanupDelay < UtcNow - Expired device flow codes
- Expired pushed authorization requests
- Expired server-side sessions
It does not remove:
- Active (non-expired) refresh tokens that have been marked consumed — these are retained for threat detection unless
RemoveConsumedTokens = true
Grant Lifecycle States
One-time-use grants (authorization codes, optionally refresh tokens) use the consumption mechanism instead of immediate deletion to enable replay detection and grace periods. The Data property of persisted grants is the authoritative payload — other properties like Created and Expiration are read-only indices. Modifying index properties directly in the database will not change runtime behavior.
Multi-Node Cleanup Conflicts
When multiple nodes all run cleanup at the same interval, they race to delete the same rows. FuzzTokenCleanupStart = true (the default) randomises the first cleanup run within the interval window. For very high-scale deployments, consider disabling cleanup on all nodes and running it as a dedicated background job:
Persisted Grant Service
For higher-level programmatic access to grants (e.g., building an admin UI or user consent management page), use IPersistedGrantService rather than querying IPersistedGrantStore directly:
This service abstracts and aggregates different grant types (authorization codes, refresh tokens, reference tokens, consent) into a unified API. It is the recommended way to implement user-facing grant/consent management rather than querying the low-level IPersistedGrantStore.
Multi-Tenant Patterns
Multi-tenant IdentityServer deployments require careful consideration of store boundaries.
Shared Database (Recommended for Most Cases)
A single ConfigurationDbContext and PersistedGrantDbContext shared across all tenants. Tenant isolation is enforced at the application layer by scoping queries to a TenantId column.
Database-per-Tenant
Each tenant gets its own connection string and EF DbContext instance. This provides the strongest data isolation, is appropriate for compliance requirements (GDPR data residency, SOC2 segmentation), and simplifies tenant offboarding.
Store Implementation Decision Matrix
Common Pitfalls
Missing MigrationsAssembly — The most common EF setup error. When migrations live in the host project (not in Duende.IdentityServer.EntityFramework), you must call sql.MigrationsAssembly(migrationsAssembly). Without this, dotnet ef migrations add and runtime startup fail.
Calling AddConfigurationStoreCache() without AddInMemoryCaching() — AddConfigurationStoreCache() wraps the EF stores automatically and includes its own IMemoryCache registration. AddInMemoryCaching() is needed when you are manually registering caching decorators on custom stores with AddClientStoreCache<T>().
In-memory caching in multi-node deployments — The default IMemoryCache-backed cache is node-local. If you update a client configuration and one node caches the old value, token requests on that node will use the stale configuration until the cache expires. Use a distributed cache (IDistributedCache) to share cache state, or set a short expiration and accept eventual consistency.
Not enabling server-side sessions before the operational store — AddServerSideSessions() must be called before or alongside AddOperationalStore(). Reversing the order or omitting AddServerSideSessions() means session data is never persisted, and session management features silently degrade.
Assuming EnableTokenCleanup = true removes consumed tokens — By default, consumed tokens are not cleaned up. You must also set RemoveConsumedTokens = true. Consumed tokens from one-time-use refresh token flows will otherwise accumulate indefinitely.
Rotating Data Protection keys without migrating encrypted grant data — Signing keys and grant payloads encrypted with an old Data Protection key become unreadable after key rotation. Always keep retired Data Protection keys available for decryption for at least as long as the longest-lived grant (typically refresh token lifetime).
Running EF migrations in multi-instance startup — Calling Database.Migrate() in Program.cs on every startup causes migration races in multi-node deployments. Run migrations as a deployment pre-step (e.g., a Kubernetes init container or a CI/CD migration job), not in the application startup path.
Using in-memory stores in production — AddInMemoryClients(), AddInMemoryApiResources(), etc. are designed for development and testing only. In-memory stores cannot be updated at runtime without restarting the application and do not survive restarts.
Resources
- Data Stores & Persistence overview — authoritative top-level docs
- Configuration Data — store interfaces, custom registration, caching, in-memory stores
- Operational Data — grants, signing keys, server-side sessions, custom store registration
- EF Core Integration —
AddConfigurationStore,AddOperationalStore,OperationalStoreOptions, schema options, token cleanup options - EF Quickstart — end-to-end walkthrough including migration creation
- ISigningKeyStore reference
- IServerSideSessionStore reference
- IPersistedGrantStore reference
- Key Management fundamentals
- Server-Side Sessions overview
- Duende EF migrations sample — reference SQL Server migration project maintained by Duende
- Related skill:
identityserver-configuration— client and resource model configuration - Related skill:
efcore-patterns— EF Core best practices applicable toConfigurationDbContextandPersistedGrantDbContext - Related skill:
database-performance— indexing, query optimization for high-write operational tables


