();
+foreach (var instance in new[] { SampleStorage.Configuration, SampleStorage.Operational })
+{
+ var schema = await schemaFactory.GetStorageInstanceSchema(instance, CancellationToken.None);
+ await schema.MigrateAsync(CancellationToken.None);
+}
+```
+
+In production, each instance would typically be a separate database and connection string, for example `AddPostgreSql()` for one instance and `AddMsSql()` for another, migrated separately and independently scaled. Each instance can use a different storage provider.
## Client credentials and the region extension
diff --git a/IdentityServer/v8/Storage/SamplePage.cs b/IdentityServer/v8/Storage/SamplePage.cs
index 246fb932..86b98cab 100644
--- a/IdentityServer/v8/Storage/SamplePage.cs
+++ b/IdentityServer/v8/Storage/SamplePage.cs
@@ -31,9 +31,19 @@ internal static string Create(string baseUrl)
Storage-backed IdentityServer
- This host stores IdentityServer configuration and operational data in an in-memory SQLite database.
- Startup registers a fixed in-memory schema, then creates scopes, identity resources, clients, and typed
- client extension-property values through the administration APIs.
+ This host stores IdentityServer configuration and operational data in two separate in-memory
+ SQLite databases, one per named storage instance. Startup registers a fixed in-memory schema,
+ then creates scopes, identity resources, clients, and typed client extension-property values
+ through the administration APIs.
+
+
+ Storage instances
+
+ This sample registers two named Duende Storage instances, each its own in-memory SQLite
+ database migrated separately: {{SampleStorage.Configuration}} holds clients,
+ identity resources, and API scopes; {{SampleStorage.Operational}} holds server-side
+ sessions, persisted grants, and signing keys. /sample/clients reads from the
+ configuration instance and /sample/sessions reads from the operational instance.
Inspect configuration
diff --git a/IdentityServer/v8/Storage/SampleStorage.cs b/IdentityServer/v8/Storage/SampleStorage.cs
new file mode 100644
index 00000000..4aaeba76
--- /dev/null
+++ b/IdentityServer/v8/Storage/SampleStorage.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Duende Software. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using Duende.Storage;
+
+namespace Storage;
+
+///
+/// Names the two Duende Storage instances this sample registers: one for IdentityServer
+/// configuration data and one for operational data.
+///
+internal static class SampleStorage
+{
+ internal static readonly StorageInstanceId Configuration = StorageInstanceId.Create("configuration");
+ internal static readonly StorageInstanceId Operational = StorageInstanceId.Create("operational");
+}
diff --git a/IdentityServer/v8/Storage/Storage.csproj b/IdentityServer/v8/Storage/Storage.csproj
index eabd93e5..753772ce 100644
--- a/IdentityServer/v8/Storage/Storage.csproj
+++ b/IdentityServer/v8/Storage/Storage.csproj
@@ -8,8 +8,8 @@
-
-
+
+
diff --git a/IdentityServer/v8/UserManagement/AccountLockout/AccountLockout/AccountLockout.csproj b/IdentityServer/v8/UserManagement/AccountLockout/AccountLockout/AccountLockout.csproj
index f9c7ac08..13e40b0a 100644
--- a/IdentityServer/v8/UserManagement/AccountLockout/AccountLockout/AccountLockout.csproj
+++ b/IdentityServer/v8/UserManagement/AccountLockout/AccountLockout/AccountLockout.csproj
@@ -7,9 +7,9 @@
-
-
-
+
+
+
diff --git a/IdentityServer/v8/UserManagement/AccountLockout/AccountLockout/Program.cs b/IdentityServer/v8/UserManagement/AccountLockout/AccountLockout/Program.cs
index 3927d1d1..8175290a 100644
--- a/IdentityServer/v8/UserManagement/AccountLockout/AccountLockout/Program.cs
+++ b/IdentityServer/v8/UserManagement/AccountLockout/AccountLockout/Program.cs
@@ -61,6 +61,8 @@
options.Events.RaiseSuccessEvents = true;
})
.AddDeveloperSigningCredential(persistKey: false)
+ .AddStorage(storage => storage.AddSqlite(options => options.ConnectionString = $"Data Source={databasePath}"))
+ .AddDynamicSchemas()
.AddUserManagement(userManagement =>
{
userManagement.Authentication(authentication =>
@@ -74,8 +76,6 @@
_ = authentication.UseSmtpOtpDispatcher(options => builder.Configuration.GetSection("Smtp").Bind(options));
});
-
- userManagement.AddSqliteStore(options => options.ConnectionString = $"Data Source={databasePath}");
});
// Replace the default passkey sign-in step so policy runs after WebAuthn verification,
@@ -89,7 +89,7 @@
var services = scope.ServiceProvider;
// A sample starts from an empty SQLite database, so create storage, schema, and users in order.
- await services.GetRequiredService().MigrateAsync(CancellationToken.None);
+ await services.GetRequiredService().MigrateAsync(CancellationToken.None);
await services.GetRequiredService().BootstrapAsync(CancellationToken.None);
await services.GetRequiredService().SeedAsync(CancellationToken.None);
}