Getting Started with Duende Storage
AddStorage(...) registers Duende Storage for both
configuration data and
operational data. Both store families use the same database provider and schema.
Install Duende Storage NuGet Packages
Section titled “Install Duende Storage NuGet Packages”Install the IdentityServer preview and one database provider. This example uses SQLite:
# Terminaldotnet add package Duende.IdentityServer --prereleasedotnet add package Duende.Storage.Sqlite --prereleaseThe packages are available from the Duende.IdentityServer and Duende.Storage.Sqlite NuGet Gallery pages.
Add a Connection String
Section titled “Add a Connection String”Add a connection string for the provider:
{ "ConnectionStrings": { "IdentityServer": "Data Source=identityserver.db" }}This SQLite connection string is suitable for local development. Keep production credentials out of source control and load them from your deployment platform’s secret store.
Register Duende Storage
Section titled “Register Duende Storage”Call AddStorage(...) on the IIdentityServerBuilder and register one database provider:
using Duende.IdentityServer;using Duende.Storage.Schema;using Duende.Storage.Sqlite;
var builder = WebApplication.CreateBuilder(args);
builder.Services .AddIdentityServer() .AddStorage(storage => storage.AddSqliteStore(options => options.ConnectionString = builder.Configuration.GetConnectionString("IdentityServer") ?? throw new InvalidOperationException( "IdentityServer connection string is missing.")));
var app = builder.Build();
if (app.Environment.IsDevelopment()){ await app.Services .GetRequiredService<IDatabaseSchema>() .MigrateAsync(CancellationToken.None);}
app.UseIdentityServer();app.Run();Do not hide a missing connection string or continue startup after a migration failure.
The example runs migrations from the application only in development. Do not give the production application schema creation permissions unless application-managed migrations are an intentional deployment choice.
Configuration Stores
Section titled “Configuration Stores”AddStorage(...) registers storage-backed implementations of:
IClientStoreIResourceStoreIIdentityProviderStoreISamlServiceProviderStoreICorsPolicyService
It also registers the configuration administration APIs.
Operational Stores
Section titled “Operational Stores”AddStorage(...) also registers:
IPersistedGrantStoreIDeviceFlowStoreIPushedAuthorizationRequestStoreIServerSideSessionStoreISigningKeyStoreISamlSigninStateStoreISamlLogoutSessionStore
Call AddServerSideSessions separately when you want IdentityServer to
use server-side sessions.
The provider adds a background purge service. Purging is enabled by default, runs hourly, deletes 100 expired entities
per batch and fuzzes its initial start time to reduce collisions between nodes. Configure StoragePurgeOptions before
calling AddStorage(...) to tune those values:
using Duende.IdentityServer.Configuration;
builder.Services.Configure<StoragePurgeOptions>(options =>{ options.PurgeInterval = TimeSpan.FromMinutes(30); // Default: 60 minutes options.BatchSize = 200; // Default: 100});Set EnablePurge to false when an external job owns cleanup.
Override an Individual Store
Section titled “Override an Individual Store”Call an explicit store registration after AddStorage(...) to replace only that store. For example:
builder.Services .AddIdentityServer() .AddStorage(storage => storage.AddSqliteStore(options => options.ConnectionString = connectionString)) .AddInMemoryClients(clients);This replaces the Duende Storage-backed client store while leaving the operational and other configuration stores in Duende Storage.
Deploy the Database Schema
Section titled “Deploy the Database Schema”IDatabaseSchema.MigrateAsync creates or upgrades the common Duende Storage schema. It requires permissions to create and
alter database objects. In production, run migrations as a controlled deployment step before application instances start.
The runtime application identity can then use narrower data access permissions.
The preview Duende CLI can inspect the current schema, generate migration SQL or apply pending migrations for SQL Server, PostgreSQL and SQLite. Install it and run it from a restored project that references Duende Storage so it detects the matching plugin version:
# Terminaldotnet tool install --global Duende.Cli --prerelease$env:DUENDE_STORAGE_CONNECTION_STRING = "<deployment-connection-string>"
duende storage migrate --provider mssql --dry-runduende storage migrate --provider mssqlUse postgresql or sqlite for the other supported CLI providers. Add --schema when you use a non-default SQL Server or
PostgreSQL schema. The --dry-run output can be reviewed and applied by a database administrator instead of granting DDL
permissions to the application.
On first use, the CLI downloads the matching Duende.Storage.CliPlugin package from NuGet and caches it. Pre-populate the
package cache when a deployment agent cannot access NuGet.
The preview CLI does not currently support Oracle migrations. For Oracle, use IDatabaseSchema.BuildMigrationScript from
a restricted deployment utility to generate SQL for review and application by your database administrator.
Run only one migration process at a time. After applying a migration, MigrateAsync verifies that the database matches the
expected schema and fails when it finds discrepancies.
Supported Databases
Section titled “Supported Databases”The Duende Storage overview lists the
published database packages and registration methods. Replace the SQLite package and AddSqliteStore call with the
provider for your database.
SQL Server, PostgreSQL and Oracle use their provider-native connection factory or data source registrations. Keep credentials outside source control and use your deployment platform’s secret store.
Protect Stored Data
Section titled “Protect Stored Data”Operational records contain tokens, grants, session data and signing material. Restrict database access, encrypt connections and backups and avoid logging stored payloads or secrets.
Use Duende User Management
Section titled “Use Duende User Management”Duende User Management uses the same Duende Storage abstractions as
IdentityServer. When AddStorage(...) has already registered the provider, call AddUserManagement(...) without
registering the same provider a second time:
builder.Services .AddIdentityServer() .AddStorage(storage => storage.AddSqliteStore(options => options.ConnectionString = connectionString)) .AddUserManagement(_ => { });IdentityServer and User Management can share the physical database, connection string and common Duende Storage schema. Their entities use different entity types within the storage layer.
When Spaces is enabled, User Management repositories use the same space-aware storage factory as IdentityServer. User profiles, authenticators, roles and groups are therefore stored in the resolved space’s pool by default.
If users must be shared globally across spaces, do not rely on this default routing. Use a deliberately separate host or storage architecture for the shared user directory. The built-in integration does not provide a per-product switch that opts only User Management out of the current space pool.
Sample
Section titled “Sample”For a complete, runnable IdentityServer application that stores and administers configuration and operational data with Duende Storage, see the Storage sample.