SQLite
Install the Package
Section titled “Install the Package”# Terminaldotnet add package Duende.Storage.Sqlite --prereleaseThe package is available from the Duende.Storage.Sqlite NuGet Gallery page.
Use a File-Based Database for Local Development
Section titled “Use a File-Based Database for Local Development”Add a connection string for a local database file:
{ "ConnectionStrings": { "IdentityServer": "Data Source=identityserver.db" }}Register the provider and route configuration and operational data to it:
using Duende.IdentityServer;using Duende.Storage.Sqlite;
var connectionString = builder.Configuration.GetConnectionString("IdentityServer")!;
builder.Services .AddIdentityServer() .AddStorage(storage => storage.AddSqlite(options => options.ConnectionString = connectionString)) .AddConfigurationStorage() .AddOperationalStorage();In development, migrate the schema at application startup:
using Duende.Storage.Schema;
if (app.Environment.IsDevelopment()){ await app.Services .GetRequiredService<IStorageInstanceSchema>() .MigrateAsync(CancellationToken.None);}Or run the schema deployment step with the Duende CLI:
# Terminalduende storage migrate --provider sqlite --connection-string "Data Source=identityserver.db"The first run against a fresh file creates the schema:
Connecting to sqlite database...Current schema version: 0Required schema version: 2Migrating from version 0 to version 2...Migration complete. Schema is now at version 2.Running the same command again is a no-op:
Connecting to sqlite database...Current schema version: 2Required schema version: 2Schema is already up to date (version 2).SQLite doesn’t support schemas, so the CLI rejects --schema for the sqlite provider. See
Create and Upgrade the Schema
for the general migration API.
Delete the database file to reset it. The application process needs write access to the file and to the folder it lives in, because SQLite also creates journal files there while it writes.
Use an In-Memory Database for Tests
Section titled “Use an In-Memory Database for Tests”AddSqliteInMemory() creates a uniquely named, shared-cache, in-memory database for each registration. Call
AddSqliteInMemory("name") to choose the name yourself instead of a generated one.
// Test setupusing Duende.IdentityServer;using Duende.Storage.Sqlite;
services .AddIdentityServer() .AddStorage(storage => storage.AddSqliteInMemory()) .AddConfigurationStorage() .AddOperationalStorage();
// Or choose the name yourself instead of a generated one:// storage.AddSqliteInMemory("shared-test-db")Storage keeps a connection open for the lifetime of the ServiceProvider to keep the in-memory database alive.
Disposing the provider closes that connection, and the database disappears with it. The data never touches disk.
There is no file-backed variant of in-memory mode.
Isolate Each Test
Section titled “Isolate Each Test”Build a new ServiceProvider with a parameterless AddSqliteInMemory() call for each test. Because xUnit creates a
new test-class instance per test, each test gets its own uniquely named database and starts from an empty schema.
If you call AddSqliteInMemory("name") with the same name while another provider that used that name is still
alive, the two providers share the same database, including tests running in parallel in the same test process. Only
reuse a name when you deliberately want two providers to share state.
Dispose the provider when the test finishes to free the database:
public async ValueTask DisposeAsync() => await _serviceProvider.DisposeAsync();Migrate the Schema in Test Setup
Section titled “Migrate the Schema in Test Setup”Every new in-memory database starts empty. Call MigrateAsync before resolving and using any store.
Example: Testing with xUnit v3
Section titled “Example: Testing with xUnit v3”This example uses xUnit v3. Install the test packages:
# Terminaldotnet add package xunit.v3dotnet add package xunit.runner.visualstudiodotnet add package Microsoft.NET.Test.Sdkdotnet add package Duende.IdentityServer --prereleasedotnet add package Duende.Storage.Sqlite --prereleaseEach test in this example gets a new in-memory database, migrated in InitializeAsync, then stores and reads back a
persisted grant through IPersistedGrantStore. DisposeAsync disposes the provider, which removes the database.
using Duende.IdentityServer;using Duende.IdentityServer.Models;using Duende.IdentityServer.Stores;using Duende.Storage.Schema;using Duende.Storage.Sqlite;using Microsoft.Extensions.DependencyInjection;using Xunit;
public class PersistedGrantStoreTests : IAsyncLifetime{ private ServiceProvider _serviceProvider = null!;
public async ValueTask InitializeAsync() { var services = new ServiceCollection(); services.AddLogging(); services.AddIdentityServer() .AddStorage(s => s.AddSqliteInMemory()) .AddConfigurationStorage() .AddOperationalStorage();
_serviceProvider = services.BuildServiceProvider();
var schema = _serviceProvider.GetRequiredService<IStorageInstanceSchema>(); await schema.MigrateAsync(TestContext.Current.CancellationToken); }
[Fact] public async Task Stores_and_reads_back_a_persisted_grant() { using var scope = _serviceProvider.CreateScope(); var store = scope.ServiceProvider.GetRequiredService<IPersistedGrantStore>();
var grant = new PersistedGrant { Key = "test-key", Type = "refresh_token", ClientId = "test-client", SubjectId = "test-subject", CreationTime = DateTime.UtcNow, Expiration = DateTime.UtcNow.AddMinutes(10), Data = "{}", };
await store.StoreAsync(grant, TestContext.Current.CancellationToken);
var found = await store.GetAsync(grant.Key, TestContext.Current.CancellationToken);
Assert.NotNull(found); Assert.Equal(grant.ClientId, found!.ClientId); }
public async ValueTask DisposeAsync() => await _serviceProvider.DisposeAsync();}Run dotnet test to run it. Reuse the same setup to test your own code that depends on IdentityServer’s stores.
Provider Notes
Section titled “Provider Notes”SqliteStorageEngineOptionshas no schema option. SQLite stores everything in the single database file or in-memory database you connect to.- Every connection runs
PRAGMA foreign_keys = ON, so foreign key constraints are enforced.