Getting Started with Duende Storage
AddStorage(...) selects a database provider for a storage instance.
AddConfigurationStorage() and
AddOperationalStorage() then route IdentityServer’s configuration and
operational data to an instance and register the matching stores. By default, all three target the same, default
instance, so a typical application uses one database. See
Multiple Storage Instances to route
categories to separate databases.
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 to select a database provider for the default storage instance,
then call AddConfigurationStorage() and AddOperationalStorage() to route configuration and operational data to it:
using Duende.IdentityServer;using Duende.Storage.Schema;using Duende.Storage.Sqlite;
var builder = WebApplication.CreateBuilder(args);
builder.Services .AddIdentityServer() .AddStorage(storage => storage.AddSqlite(options => options.ConnectionString = builder.Configuration.GetConnectionString("IdentityServer") ?? throw new InvalidOperationException( "IdentityServer connection string is missing."))) .AddConfigurationStorage() .AddOperationalStorage();
var app = builder.Build();
if (app.Environment.IsDevelopment()){ await app.Services .GetRequiredService<IStorageInstanceSchema>() .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”AddConfigurationStorage() registers storage-backed implementations of:
IClientStoreIResourceStoreIIdentityProviderStoreISamlServiceProviderStoreICorsPolicyService
It also registers the configuration administration APIs.
Operational Stores
Section titled “Operational Stores”AddOperationalStorage() also registers:
IPersistedGrantStoreIDeviceFlowStoreIPushedAuthorizationRequestStoreIServerSideSessionStoreISigningKeyStoreISamlSigninStateStoreISamlLogoutSessionStore
Call AddServerSideSessions separately when you want IdentityServer to
use server-side sessions.
The provider adds a background purge service that runs against every registered storage instance. 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 AddOperationalStorage() 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 AddConfigurationStorage()/AddOperationalStorage() to replace only that
store. For example:
builder.Services .AddIdentityServer() .AddStorage(storage => storage.AddSqlite(options => options.ConnectionString = connectionString)) .AddConfigurationStorage() .AddOperationalStorage() .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”IStorageInstanceSchema.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 IStorageInstanceSchema.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 AddSqlite 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 a provider for the default instance, call
AddUserManagement(...) without registering the same provider a second time: it maps the user-management data category
to that instance and reuses its database.
builder.Services .AddIdentityServer() .AddStorage(storage => storage.AddSqlite(options => options.ConnectionString = connectionString)) .AddConfigurationStorage() .AddOperationalStorage() .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.
To store IdentityServer and User Management data in separate databases instead of sharing one, see Multiple Storage Instances.
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.