DB Contexts in IdentityServer

Entity Framework Core’s default behavior is to create all tables in the default database schema. When working with Duende IdentityServer, you may want to organize your data in separate schemas for better organization and security. IdentityServer’s standard configuration uses two separate DbContexts for different types of data. The ConfigurationDbContext is responsible for storing configuration data such as clients, resources, and scopes. The PersistedGrantDbContext stores operational data including grants, server sessions, and managed keys. By default, all tables are created in the default dbo schema but you can modify this behavior using the options available for each context.

Modifying the Default Configuration

Using the standard pattern as found in the Duende Entity Framework project template the AddConfigurationStore method accepts an action to modify settings on a ConfigurationStoreOptions object. This is normally used to configure the DbContext’s connection but also exposes a DefaultSchema property which handles changing the schema for all tables.

builder.Services.AddIdentityServer()
    .AddConfigurationStore(options =>
    {
        options.ConfigureDbContext = b =>
        {
            b.UseSqlServer(connectionString, dbOpts => dbOpts.MigrationsAssembly(typeof(Program).Assembly.FullName));
        };
        options.DefaultSchema = "config";
    });

PersistedGrantDbContext uses the same pattern with the AddOperationalStore method.

    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = b =>
        {
            b.UseSqlServer(connectionString, dbOpts => dbOpts.MigrationsAssembly(typeof(Program).Assembly.FullName));
        };
        options.DefaultSchema = "ops";
    });

The two contexts can share the same schema or each be set to a different schema.

Database Migrations

Running IdentityServer following these changes will cause runtime errors because the tables have not been moved to the new schemas yet. If using EF Core Migrations the changes can be added as a new migration and then applied to move the existing tables to their new schemas. Depending on your project setup this may vary, but for the standard Duende template the commands to run in the main project folder look like:

dotnet ef migrations add -c ConfigurationDbContext CustomSchema
dotnet ef migrations add -c PersistedGrantDbContext CustomSchema

The template includes an optional /seed startup parameter that can be used to apply migrations directly from the IdentityServer application or scripts can be generated to execute against the database externally.

dotnet ef migrations script -c ConfigurationDbContext --idempotent --output Migrations/ConfigurationDb.sql
dotnet ef migrations script -c PersistedGrantDbContext --idempotent --output Migrations/PersistedGrantDb.sql

After the migrations have been applied, the tables should show under their new schemas while preserving any existing data.

SQL Tables Following Migration

Keep in mind that because this change is part of the code rather than the connection string, the schema name should be consistent across environments to avoid deployment issues. If required, the schemas can be set dynamically from configuration values but this will complicate applying migrations across the environments. Also remember that you may need to update your database permissions to reflect the new table structure: application database users will need access to the new schemas.

Code Versions

Example code is using .NET 8 with these library versions:

  • Duende.IdentityServer.EntityFramework 7.0.7
  • Microsoft.EntityFrameworkCore.SqlServer 8.0.10
  • Microsoft.EntityFrameworkCore.Tools 8.0.10