Using IdentityServer4 with custom DBContext config

I created a custom one IConfigurationDbContext

for using IDS4 with Oracle.

  public class IdentityConfigurationDbContext :  DbContext, IConfigurationDbContext {
        private readonly ConfigurationStoreOptions storeOptions;

        public IdentityConfigurationDbContext(DbContextOptions<IdentityServerDbContext> options)
         : base(options) {
    }

    public IdentityConfigurationDbContext(DbContextOptions<ConfigurationDbContext> options, ConfigurationStoreOptions storeOptions)
        : base(options) {
        this.storeOptions = storeOptions ?? throw new ArgumentNullException(nameof(storeOptions));
    }

    public DbSet<Client> Clients { get; set; }
    public DbSet<IdentityResource> IdentityResources { get; set; }
    public DbSet<ApiResource> ApiResources { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        modelBuilder.ConfigureClientContext(storeOptions);
        modelBuilder.ConfigureResourcesContext(storeOptions);

        base.OnModelCreating(modelBuilder);
    }
  }

      

in ConfigureService:

 services.AddIdentityServer()
                .AddTemporarySigningCredential()
                .AddAspNetIdentity<ApplicationUser>();

      

I also have a custom IClientStore

one that is added to the container like this:

services.AddScoped<IClientStore, ClientStore>();

      

when i run migration IdentityConfigurationDbContext

i get this error:

System.InvalidOperationException: No database provider has been configured for this DbContext.

      

I tried to do this:

services.AddDbContext<IdentityConfigurationDbContext>(builder => builder.UseOracle(connectionString, options => {
                options.MigrationsAssembly(migrationsAssembly);
                options.MigrationsHistoryTable("EF_MIGRATION_HISTORY");
            }));

      

Is this the right way to use a custom dbcontext with IDS4? and how to fix this issue and complete the migration work?

+9


source to share


4 answers


I've tried a different approach. Instead of implementing, IConfigurationDbContext

I inherited fromIdentityServer4.EntityFramework.DbContexts.ConfigurationDbContext

public class CustomConfigurationDbContext : ConfigurationDbContext
{
    public CustomConfigurationDbContext(DbContextOptions<ConfigurationDbContext> options,
        ConfigurationStoreOptions storeOptions)
        : base(options, storeOptions)
    {
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            //...

            base.OnConfiguring(optionsBuilder);
        }
    }
}

      

And in startup.cs



services.AddIdentityServer()
                .AddTemporarySigningCredential()
                .AddConfigurationStore(
                    builder => builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)))
                .AddOperationalStore(
                    builder => builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)))
                .AddAspNetIdentity<ApplicationUser>();

      

It works like a charm. Disclaimer: This is not my idea. I just can't remember the source of this.

+3


source


You don't need to create a custom ConfigurationDbContext

or event IDbContextFactory

to switch to use different databases. Since IdentityServer4.EntityFramework

version 2.3.2 you can do:

namespace DL.STS.Host
{
    public class Startup
    {
        ...

        public void ConfigureServices(IServiceCollection services)
        {
            string connectionString = _configuration.GetConnectionString("appDbConnection");

            string migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly
                .GetName().Name;

            services
               .AddIdentityServer()
               .AddConfigurationStore(options =>
               {
                   options.ConfigureDbContext = builder =>
                       // I made up this extension method "UseOracle",
                       // but this is where you plug your database in
                       builder.UseOracle(connectionString,
                           sql => sql.MigrationsAssembly(migrationsAssembly));
               })
               ...;

            ...
        }

        ...
    }
}

      

Serve config / live storage to your own project / assembly?

What if you want to neatly decompose your solution and want to split the configuration store and operational store (as well as the user ID store) into their own class library / assembly?

According to the documentation, you can use -o

to specify the destination of the migration output folder:

dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb

      

But who likes to memorize / type such a long way when doing migrations? Then you might be thinking: how about a custom ConfigurationDbContext

one inherited from IdentityServer and a separate project:

using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.EntityFramework.Options;
using Microsoft.EntityFrameworkCore;

namespace DL.STS.Data.ConfigurationStore.EFCore
{
    public class AppConfigurationDbContext : ConfigurationDbContext
    {
        public AppConfigurationDbContext(DbContextOptions<ConfigurationDbContext> options, 
            ConfigurationStoreOptions storeOptions) : base(options, storeOptions)
        {
        }
    }
}

      

Common mistakes

I think this is where people get into trouble. When you do Add-Migration

, you may encounter:

Unable to create object of type AppConfigurationDbContext

. For the various templates supported at design time see https://go.microsoft.com/fwlink/?linkid=851728 .



or

Failed to resolve service for type Microsoft.EntityFrameworkCore.DbContextOptions<IdentityServer4.EntityFramework.DbContexts.ConfigurationDbContext>

when trying to activate DL.STS.Data.ConfigurationStore.EFCore.AppConfigurationDbContext

.

I don't think there is a way to fix this right now.

Are there other ways?

It turns out it's actually pretty simple. It looks like you cannot have your own DbContext

inherited from IdentityServer. So get rid of that and create an extension method in this separate library / assembly:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

namespace DL.STS.Data.ConfigurationStore.EFCore.Extensions
{
    public static class IdentityServerBuilderExtensions
    {
        public static IIdentityServerBuilder AddEFConfigurationStore(
            this IIdentityServerBuilder builder, string connectionString)
        {
            string assemblyNamespace = typeof(IdentityServerBuilderExtensions)
                .GetTypeInfo()
                .Assembly
                .GetName()
                .Name;

            builder.AddConfigurationStore(options =>
                options.ConfigureDbContext = b =>
                    b.UseSqlServer(connectionString, optionsBuilder =>
                        optionsBuilder.MigrationsAssembly(assemblyNamespace)
                    )
            );

            return builder;
        }
    }
}

      

Then on Startup.cs

in your web project:

public void ConfigureServices(IServiceCollection services)
{
    ...

    string connectionString = _configuration.GetConnectionString("appDbConnection");

    services
        .AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddEFConfigurationStore(connectionString)
        ...;

    ...
}

      

And when you do PM> Add-Migration AddConfigurationTables -Context ConfigurationDbContext

with the default project, which is a separate library / assembly:

enter image description here

+2


source


Adding IDbContextFactory

fixes the problem.

public class IdentityConfigurationDbContextFactory : IDbContextFactory<IdentityConfigurationDbContext> {

        public IdentityConfigurationDbContext Create(DbContextFactoryOptions options) {
            var optionsBuilder = new DbContextOptionsBuilder<ConfigurationDbContext>();
            var config = new ConfigurationBuilder()
                             .SetBasePath(options.ContentRootPath)
                             .AddJsonFile("appsettings.json")
                              .AddJsonFile($"appsettings.{options.EnvironmentName}.json", true)

                             .Build();

            optionsBuilder.UseOracle(config.GetConnectionString("DefaultConnection"));

            return new IdentityConfigurationDbContext(optionsBuilder.Options, new ConfigurationStoreOptions());
        }
    }

      

+1


source


In the latest release, the Identityserver framework supports a custom implementation of the configuration store, operations store. This will work with migrations too

see below for example

            public class CustomPersistsDbContext : DbContext, IPersistedGrantDbContext
                {
                }

      

In OnModelCreating (ModelBuilder modelBuilder) I had to add relationships manually:

                protected override void OnModelCreating(ModelBuilder modelBuilder)
                {
                    //Optional: The version of .NET Core, used by Ef Core Migration history table
                    modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");

          //.. Your custom code

    //PersistentDbContext
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
                    {
                        b.Property<string>("UserCode")
                            .ValueGeneratedOnAdd()
                            .HasMaxLength(200);

                        b.Property<string>("ClientId")
                            .IsRequired()
                            .HasMaxLength(200);

                        b.Property<DateTime>("CreationTime");

                        b.Property<string>("Data")
                            .IsRequired()
                            .HasMaxLength(50000);

                        b.Property<string>("DeviceCode")
                            .IsRequired()
                            .HasMaxLength(200);

                        b.Property<DateTime?>("Expiration")
                            .IsRequired();

                        b.Property<string>("SubjectId")
                            .HasMaxLength(200);

                        b.HasKey("UserCode");

                        b.HasIndex("DeviceCode")
                            .IsUnique();

                        b.HasIndex("UserCode")
                            .IsUnique();

                        b.ToTable("DeviceCodes");
                    });

                    modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
                    {
                        b.Property<string>("Key")
                            .HasMaxLength(200);

                        b.Property<string>("ClientId")
                            .IsRequired()
                            .HasMaxLength(200);

                        b.Property<DateTime>("CreationTime");

                        b.Property<string>("Data")
                            .IsRequired()
                            .HasMaxLength(50000);

                        b.Property<DateTime?>("Expiration");

                        b.Property<string>("SubjectId")
                            .HasMaxLength(200);

                        b.Property<string>("Type")
                            .IsRequired()
                            .HasMaxLength(50);

                        b.HasKey("Key");

                        b.HasIndex("SubjectId", "ClientId", "Type");

                        b.ToTable("PersistedGrants");
                    });
                }

      

When starting services

 .AddOperationalStore<CustomPersistsDbContext>(options =>

      

+1


source







All Articles