Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This page describes preview packages and APIs that are subject to change. Start
[Duende Storage overview](/identityserver/data/providers/duende-storage/index.mdx) for the preview scope.
:::

[`AddStorage`](/identityserver/data/providers/duende-storage/getting-started.md#register-duende-storage) registers
[`AddConfigurationStorage`](/identityserver/data/providers/duende-storage/getting-started.md#configuration-stores) registers
administration services alongside the stores that IdentityServer uses at runtime. These are .NET service APIs, not
preconfigured HTTP endpoints. You decide how to expose them through a protected administration application, command-line
tool or deployment process.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ This page describes preview packages and APIs that are subject to change. Start
[Duende Storage overview](/identityserver/data/providers/duende-storage/index.mdx) for the preview scope.
:::

`AddStorage(...)` registers Duende Storage for both
[configuration data](/identityserver/data/configuration.mdx) and
[operational data](/identityserver/data/operational.md). Both store families use the same database provider and schema.
`AddStorage(...)` selects a database provider for a storage instance.
[`AddConfigurationStorage()`](/identityserver/data/configuration.mdx) and
[`AddOperationalStorage()`](/identityserver/data/operational.md) 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](/identityserver/data/providers/duende-storage/multiple-storage-instances.md) to route
categories to separate databases.

## Install Duende Storage NuGet Packages

Expand Down Expand Up @@ -51,7 +55,8 @@ load them from your deployment platform's secret store.

## Register Duende Storage

Call `AddStorage(...)` on the `IIdentityServerBuilder` and register one database provider:
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:

```csharp
// Program.cs
Expand All @@ -64,18 +69,20 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddIdentityServer()
.AddStorage(storage =>
storage.AddSqliteStore(options =>
storage.AddSqlite(options =>
options.ConnectionString =
builder.Configuration.GetConnectionString("IdentityServer")
?? throw new InvalidOperationException(
"IdentityServer connection string is missing.")));
"IdentityServer connection string is missing.")))
.AddConfigurationStorage()
.AddOperationalStorage();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
await app.Services
.GetRequiredService<IDatabaseSchema>()
.GetRequiredService<IStorageInstanceSchema>()
.MigrateAsync(CancellationToken.None);
}

Expand All @@ -90,7 +97,7 @@ creation permissions unless application-managed migrations are an intentional de

### Configuration Stores

`AddStorage(...)` registers storage-backed implementations of:
`AddConfigurationStorage()` registers storage-backed implementations of:

- `IClientStore`
- `IResourceStore`
Expand All @@ -103,7 +110,7 @@ It also registers the

### Operational Stores

`AddStorage(...)` also registers:
`AddOperationalStorage()` also registers:

- `IPersistedGrantStore`
- `IDeviceFlowStore`
Expand All @@ -116,9 +123,9 @@ It also registers the
Call [`AddServerSideSessions`](/identityserver/ui/server-side-sessions/index.md) 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:
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:

```csharp
// Program.cs
Expand All @@ -135,15 +142,18 @@ Set `EnablePurge` to `false` when an external job owns cleanup.

### Override an Individual Store

Call an explicit store registration after `AddStorage(...)` to replace only that store. For example:
Call an explicit store registration after `AddConfigurationStorage()`/`AddOperationalStorage()` to replace only that
store. For example:

```csharp
// Program.cs
builder.Services
.AddIdentityServer()
.AddStorage(storage =>
storage.AddSqliteStore(options =>
storage.AddSqlite(options =>
options.ConnectionString = connectionString))
.AddConfigurationStorage()
.AddOperationalStorage()
.AddInMemoryClients(clients);
```

Expand All @@ -152,7 +162,7 @@ Duende Storage.

## Deploy the Database Schema

`IDatabaseSchema.MigrateAsync` creates or upgrades the common Duende Storage schema. It requires permissions to create and
`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.

Expand All @@ -176,7 +186,7 @@ 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
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
Expand All @@ -185,7 +195,7 @@ expected schema and fails when it finds discrepancies.
## Supported Databases

The [Duende Storage overview](/identityserver/data/providers/duende-storage/index.mdx#supported-databases) lists the
published database packages and registration methods. Replace the SQLite package and `AddSqliteStore` call with 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
Expand All @@ -207,16 +217,19 @@ connections and backups and avoid logging stored payloads or secrets.
## Use Duende User Management

[Duende User Management](/identityserver/identity/user-management/index.mdx) 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:
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.

```csharp
// Program.cs
builder.Services
.AddIdentityServer()
.AddStorage(storage =>
storage.AddSqliteStore(options =>
storage.AddSqlite(options =>
options.ConnectionString = connectionString))
.AddConfigurationStorage()
.AddOperationalStorage()
.AddUserManagement(_ => { });
```

Expand All @@ -231,6 +244,9 @@ If users must be shared globally across spaces, do not rely on this default rout
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](/identityserver/data/providers/duende-storage/multiple-storage-instances.md).

## Sample

For a complete, runnable IdentityServer application that stores and administers configuration and operational data with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ The provider has four parts:
3. IdentityServer adapters implement its configuration and operational store interfaces.
4. Optional administration APIs and schemas let your application manage and extend configuration data.

:::note[One Registration for Configuration and Operational Storage]
`AddStorage(...)` registers configuration and operational storage together against one database provider and schema. To
replace one specific store, call an explicit registration such as `AddInMemoryClients()` or
`AddInMemoryPersistedGrants()` after `AddStorage(...)`. The later registration replaces only that store and leaves the
rest of the Duende Storage wiring in place.
:::note[Choosing a Database Is Separate from Choosing What to Store in It]
`AddStorage(...)` only selects a database provider for a storage instance; it does not enable storing IdentityServer data by
itself. Call `AddConfigurationStorage()` and `AddOperationalStorage()` to route configuration and operational data to
that instance.

Both configuration and operational stores default to the same storage instance and thus,
one shared database, but each category can target a different instance. See [Multiple Storage Instances](/identityserver/data/providers/duende-storage/multiple-storage-instances.md)
for that setup. To replace one specific store, call an explicit registration such as `AddInMemoryClients()` or
`AddInMemoryPersistedGrants()` after `AddConfigurationStorage()`/`AddOperationalStorage()`. The later registration
replaces only that store and leaves the rest of the Duende Storage setup in place.
:::

## Why Use Duende Storage?
Expand All @@ -62,12 +67,12 @@ approach matches your requirements.

Choose the package that matches your database:

| Database | Package | Registration |
| ---------- | --------------------------------------------------------------------------------------- | -------------------- |
| SQL Server | [`Duende.Storage.MsSql`](https://www.nuget.org/packages/Duende.Storage.MsSql) | `AddMsSqlStore` |
| PostgreSQL | [`Duende.Storage.PostgreSql`](https://www.nuget.org/packages/Duende.Storage.PostgreSql) | `AddPostgreSqlStore` |
| Oracle | [`Duende.Storage.Oracle`](https://www.nuget.org/packages/Duende.Storage.Oracle) | `AddOracleStore` |
| SQLite | [`Duende.Storage.Sqlite`](https://www.nuget.org/packages/Duende.Storage.Sqlite) | `AddSqliteStore` |
| Database | Package | Registration |
| ---------- | --------------------------------------------------------------------------------------- | ---------------- |
| SQL Server | [`Duende.Storage.MsSql`](https://www.nuget.org/packages/Duende.Storage.MsSql) | `AddMsSql` |
| PostgreSQL | [`Duende.Storage.PostgreSql`](https://www.nuget.org/packages/Duende.Storage.PostgreSql) | `AddPostgreSql` |
| Oracle | [`Duende.Storage.Oracle`](https://www.nuget.org/packages/Duende.Storage.Oracle) | `AddOracle` |
| SQLite | [`Duende.Storage.Sqlite`](https://www.nuget.org/packages/Duende.Storage.Sqlite) | `AddSqlite` |

<CardGrid>
<LinkCard
Expand All @@ -80,6 +85,11 @@ Choose the package that matches your database:
title="Configuration Admin APIs"
description="Create and manage configuration through dependency-injected services."
/>
<LinkCard
href="/identityserver/data/providers/duende-storage/multiple-storage-instances/"
title="Multiple Storage Instances"
description="Route configuration, operational and other product data to separate databases."
/>
<LinkCard
href="/identityserver/data/providers/duende-storage/schemas/"
title="Data Extension Schemas"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ service providers and CORS origins.
4. Import API scopes and identity resources before API resources and clients that reference them.
5. Compare business identifiers, relationships and counts between both stores.
6. Stop configuration writes, perform a final import and switch the IdentityServer registration to
`AddStorage(...)`.
`AddStorage(...)` with `AddConfigurationStorage()`.

:::caution[Plan Secret Rotation]
Entity Framework Core stores client and API resource secrets as one-way hashes. The Duende Storage admin APIs accept
Expand Down
Loading
Loading