Claude
Skills
Sign in
Back

identityserver-stores

Included with Lifetime
$97 forever

Implement and customize Duende IdentityServer stores including configuration store, operational store, and Entity Framework Core integration. Covers migrations, custom store implementations, caching strategies, server-side sessions, signing key storage, token cleanup, and multi-tenant patterns.

General

What this skill does


# Duende IdentityServer Stores

## When to Use This Skill

- You are wiring up `AddConfigurationStore()` or `AddOperationalStore()` with EF Core and need correct registration, migration assembly setup, and schema configuration.
- You are implementing a custom `IClientStore`, `IResourceStore`, `IPersistedGrantStore`, or `ISigningKeyStore` against 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 `ConfigurationDbContext` and `PersistedGrantDbContext`.
- 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

```bash
dotnet add package Duende.IdentityServer.EntityFramework
```

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:

```
┌─────────────────────────────────────────────────────────────┐
│                    IdentityServer Runtime                    │
├──────────────────────────┬──────────────────────────────────┤
│    Configuration Data    │       Operational Data           │
│                          │                                  │
│  • Clients               │  • Authorization codes           │
│  • API Resources         │  • Reference tokens              │
│  • API Scopes            │  • Refresh tokens                │
│  • Identity Resources    │  • User consent                  │
│  • Identity Providers    │  • Device codes                  │
│  • CORS policies         │  • Pushed auth. requests         │
│                          │  • Signing keys                  │
│                          │  • Server-side sessions          │
├──────────────────────────┼──────────────────────────────────┤
│  ConfigurationDbContext   │  PersistedGrantDbContext          │
│  (IClientStore,          │  (IPersistedGrantStore,           │
│   IResourceStore,        │   IDeviceFlowStore,               │
│   IIdentityProviderStore,│   IPushedAuthorizationRequestStore,│
│   ICorsPolicyService)    │   IServerSideSessionStore,        │
│                          │   ISigningKeyStore)               │
└──────────────────────────┴──────────────────────────────────┘
```

### Configuration Data

Stores static, rarely-changing data that describes how IdentityServer behaves:

| Interface | Contents |
|---|---|
| `IClientStore` | OAuth/OIDC clients (grant types, redirect URIs, secrets, claims, scopes) |
| `IResourceStore` | `IdentityResource`, `ApiResource`, and `ApiScope` definitions |
| `ICorsPolicyService` | CORS allowed-origin rules (derived from client configuration) |
| `IIdentityProviderStore` | Dynamic external identity provider registrations |

### Operational Data

Stores dynamic, high-write runtime state that IdentityServer creates and manages during request processing:

| Interface | Contents |
|---|---|
| `IPersistedGrantStore` | Authorization codes, refresh tokens, reference tokens, user consent records |
| `IDeviceFlowStore` | Device authorization flow codes and user codes |
| `ISigningKeyStore` | Dynamically managed signing keys (used by automatic key management) |
| `IServerSideSessionStore` | Server-side authentication session data for interactive users |
| `IPushedAuthorizationRequestStore` | Pushed authorization request (PAR) data |

---

## EF Core Integration

The `Duende.IdentityServer.EntityFramework` NuGet package provides EF Core-backed implementations of all store interfaces. It ships two `DbContext` types:

- **`ConfigurationDbContext`** — backs `IClientStore`, `IResourceStore`, `ICorsPolicyService`, `IIdentityProviderStore`
- **`PersistedGrantDbContext`** — backs `IPersistedGrantStore`, `IDeviceFlowStore`, `ISigningKeyStore`, `IServerSideSessionStore`

### Registering Both Stores

```csharp
// ✅ Correct: register both stores with explicit migration assembly
var migrationsAssembly = typeof(Program).Assembly.GetName().Name;
var connectionString = builder.Configuration.GetConnectionString("IdentityServer");

builder.Services.AddIdentityServer()
    .AddConfigurationStore(options =>
    {
        options.ConfigureDbContext = b =>
            b.UseSqlServer(connectionString, sql =>
                sql.MigrationsAssembly(migrationsAssembly));
    })
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = b =>
            b.UseSqlServer(connectionString, sql =>
                sql.MigrationsAssembly(migrationsAssembly));

        options.EnableTokenCleanup = true;
        options.TokenCleanupInterval = 3600; // seconds; default 1 hour
    });
```

```csharp
// ❌ Wrong: omitting MigrationsAssembly when migrations live in the host project
builder.Services.AddIdentityServer()
    .AddConfigurationStore(options =>
    {
        options.ConfigureDbContext = b => b.UseSqlServer(connectionString);
        // EF will look for migrations in Duende.IdentityServer.EntityFramework.dll
        // and fail to find them
    });
```

### Separate Schemas

Isolate configuration and operational tables using `DefaultSchema` to avoid naming collisions and simplify backup strategies:

```csharp
// ✅ Recommended for production: dedicated schemas per store
builder.Services.AddIdentityServer()
    .AddConfigurationStore(options =>
    {
        options.DefaultSchema = "idscfg";
        options.ConfigureDbContext = b =>
            b.UseSqlServer(connectionString, sql =>
            {
                sql.MigrationsAssembly(migrationsAssembly);
                sql.MigrationsHistoryTable("__ConfigMigrationsHistory", "idscfg");
            });
    })
    .AddOperationalStore(options =>
    {
        options.DefaultSchema = "idso

Related in General