Finbuckle.MultiTenant icon indicating copy to clipboard operation
Finbuckle.MultiTenant copied to clipboard

Duende Identity Server Base Path Strategy

Open goforebroke opened this issue 3 years ago • 5 comments

Andrew,

I have read several of the issues regarding Duende Identity Server 6 and Finbuckle. In most of the issues, you recommend using the base path strategy (Use of claim strategy and Identity types #613). I have tried to set this up, but I keep getting a 404 error when navigating to the login page with the tenant identifier.

Here is how things are setup in with Identity server.

public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
 {
            builder.Services.AddRazorPagesServices();
            builder.Services.AddIdentityServices(builder.Configuration);
            builder.Services.AddMultiTenantServices(builder.Configuration); //Configure Finbuckle components
            builder.Services.AddMessagingServices(builder.Configuration);

            return builder.Build();
 }

IdentityServer configuration below. For now I am using in memory clients, scopes and resources for development purposes

static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration configuration)
{
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlite(configuration.GetConnectionString("IdentityConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>(options =>
            {
                options.User.RequireUniqueEmail = true;
            })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

            services
                .AddIdentityServer(options =>
                {
                    options.Events.RaiseErrorEvents = true;
                    options.Events.RaiseInformationEvents = true;
                    options.Events.RaiseFailureEvents = true;
                    options.Events.RaiseSuccessEvents = true;

                    // see https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/
                    options.EmitStaticAudienceClaim = true;
                })
                .AddInMemoryClients(Config.Clients)
                .AddInMemoryApiScopes(Config.ApiScopes)
                .AddInMemoryIdentityResources(Config.IdentityResources)
                .AddAspNetIdentity<ApplicationUser>()
                .AddProfileService<CustomProfileService>();

            return services;
}

EFCoreStore below

static IServiceCollection AddMultiTenantServices(this IServiceCollection services,
            IConfiguration configuration)
{
            services.AddDbContext<TenantDbContext>(options =>
                options.UseSqlite(configuration.GetConnectionString("TenantConnection")))
                .AddMultiTenant<MyTenant>()
                .WithEFCoreStore<TenantDbContext, MyTenant>()
                .WithBasePathStrategy(options => 
                {
                    options.RebaseAspNetCorePathBase = true;
                });

            return services;
}

Below is how the pipline is set up

public static WebApplication ConfigurePipeline(this WebApplication app)
{
            app.UseSerilogRequestLogging();

            if (app.Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseStaticFiles();
            app.UseRouting();
            app.UseIdentityServer();
            app.UseMultiTenant();
            app.UseAuthorization();
            app.MapRazorPages();

            return app;
}

Below are my two EF DB Contexts. One for Identity and the other for the EFCore Store

Identity...

public class ApplicationDbContext : MultiTenantIdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(ITenantInfo tenantInfo) : base(tenantInfo)
    {
    }
    public ApplicationDbContext(ITenantInfo tenantInfo, 
        DbContextOptions<ApplicationDbContext> options) : base(tenantInfo, options)
    {
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
    }
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

        builder.ApplyConfiguration(new ApplicationUserConfiguration());
    }
}

Store..

public class TenantDbContext : EFCoreStoreDbContext<MyTenant>
{
        public TenantDbContext(DbContextOptions<TenantDbContext> options) : base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.ApplyConfiguration(new TenantConfiguration());

        }
}

When I navigate to https://localhost:5001/Goforebroke/Account/Login, I get a 404 error.

My serilog log shows that Finbuckle discovers the tenant, but the URL is not found.

[10:46:09 Debug] Finbuckle.MultiTenant.Stores.EFCoreStore
TryGetByIdentifierAsync: Tenant found with identifier "Goforebroke"

[10:46:09 Information] Serilog.AspNetCore.RequestLoggingMiddleware
HTTP GET /Account/Login responded 404 in 184.8214 ms

Have set things up in the correct order, or am I missing something my setup?

goforebroke avatar Dec 31 '22 18:12 goforebroke

After a little more research, reading , and looking at your sample with Identity Server 4,I adjusted the order where I add "UseMultiTenant()". I have things working now

goforebroke avatar Jan 01 '23 20:01 goforebroke

Hi there, glad you got it working and sorry for the slow reply. Nice to hear from you!

AndrewTriesToCode avatar Jan 09 '23 04:01 AndrewTriesToCode

@goforebroke did you get this rolling? i am removing base path strategy while i'm updating identity server to use razor pages instead of controllers.

its like you have to get the acr tenant then add it to a temporary claim almost immediately so that it tracks through the rest of the flow? i am not always able to get the acr values tenant because that query string with returnUrl doesn't always have it.

natelaff avatar Jul 28 '23 00:07 natelaff

@natelaff sorry I have taken so long to get back to you. I have been super busy with my regular job outside of this personal project.

I have two Identity server clients, both razor applications. One client uses a static strategy (default tenant) and the other uses a base path strategy . The second client has an area where a user "sets" their tenant. My identity server is also configured to use a base path strategy. I don't pass the tenant acr value through from the clients to Identity Server. The tenant is always passed in the URL as part of the authorization/authentication request. In addition, I use a custom RedirectUriValidator in Identity server to ensure that the registered "RedirectUris" for the identity clients validate. I probably have not got far enough into my project to encounter real problems. Is there something in my setup that you would like to see?

goforebroke avatar Oct 17 '23 00:10 goforebroke