Finbuckle / Finbuckle.MultiTenant

Finbuckle.MultiTenant is an open-source multitenancy middleware library for .NET. It enables tenant resolution, per-tenant app behavior, and per-tenant data isolation.
https://www.finbuckle.com/multitenant
Apache License 2.0
1.3k stars 265 forks source link

[Question] WithRouteStrategy() tenantInfo.Identifier is Case sensitive with Identity #230

Closed akasoggybunz closed 4 years ago

akasoggybunz commented 4 years ago

Issue

When using WithRouteStrategy and identity when logging in. If I have a different case it will not login, or incorrectly route tenant. I get a Status Code: 400; Bad Request

Case

Tenant identifier = Dev

If I go to https://localhost:44310/dev get redirected to https://localhost:44310/Dev/Identity/Account/Login?returnurl=%2Fdev (To this point everything looks correct, Tenant is identified) After login it will either give the Code: 400 or will take me back to https://localhost:44310/dev If taken back here the user is not logged in, but if you go to https://localhost:44310/Dev (Notice the Case in Dev compared to dev) the user is logged in.

Question

Is my Configuration wrong? OR Is this a middle-ware issue? (Ignore case on identifiers within route)

Configuration

Here is my Finbuckle config in ConfigureServices

services.AddMultiTenant()
                .WithEFCoreStore<TenantDbContext>()
                .WithRouteStrategy()
                .WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantInfo) =>
                    {
                        // Since we are using the route strategy configure each tenant
                        // to have a different cookie name and adjust the paths.
                        options.Cookie.Path = $"/{tenantInfo.Identifier}";
                        options.Cookie.Name = $"{tenantInfo.Id}_authentication";
                        options.LoginPath = $"{options.Cookie.Path}{options.LoginPath}";
                        options.LogoutPath = $"{options.Cookie.Path}{options.LogoutPath}";
                    });
AndrewTriesToCode commented 4 years ago

Hi, what is the underlying database used for TenantDbContext? The behavior can vary based on how the underlying provider does string comparisons.

akasoggybunz commented 4 years ago
public class TenantDbContext : EFCoreStoreDbContext

with the migration

public partial class TenantDbContext : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "TenantInfo",
                columns: table => new
                {
                    Id = table.Column<string>(maxLength: 64, nullable: false),
                    Identifier = table.Column<string>(nullable: true),
                    Name = table.Column<string>(nullable: false),
                    ConnectionString = table.Column<string>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_TenantInfo", x => x.Id);
                });

            migrationBuilder.CreateIndex(
                name: "IX_TenantInfo_Identifier",
                table: "TenantInfo",
                column: "Identifier",
                unique: true,
                filter: "[Identifier] IS NOT NULL");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "TenantInfo");
        }
    }

Also now that I am looking at this. I probably don't want Identifier to be nullable, correct?

AndrewTriesToCode commented 4 years ago

Thanks for the detail. Which database provider are you using?

AndrewTriesToCode commented 4 years ago

Looking closer now I'm wondering if the Cookie Path is the culprit here. Can you try commenting out this line and let me know if it works:

 'options.Cookie.Path = $"/{tenantInfo.Identifier}";'

the downside is that if multiple tenant logins exist on the same machine a cookie for each is sent. In practice this probably isn't a serious problem. I'm debating changing the documentation to knot recommend this path adjustment.

akasoggybunz commented 4 years ago

Solved!

Commenting out the options.Cookie.Path = $"/{tenantInfo.Identifier}";' Indeed did fix this for me, thank you!

p.s. Loving Finbuckle! Kudos, and praise!

akasoggybunz commented 4 years ago

Logout is not working now, that i commented out the option.Cookie.Path Is there something else in the startup.cs, I need to configure?

AndrewTriesToCode commented 4 years ago

In your code here. since we can't changing path any more notice the login and logout originally use it?

.WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantInfo) =>
                    {
                        // Since we are using the route strategy configure each tenant
                        // to have a different cookie name and adjust the paths.
                        options.Cookie.Path = $"/{tenantInfo.Identifier}";
                        options.Cookie.Name = $"{tenantInfo.Id}_authentication";
                        options.LoginPath = $"{options.Cookie.Path}{options.LoginPath}";
                        options.LogoutPath = $"{options.Cookie.Path}{options.LogoutPath}";
                    });

So if you comment out the path just change it like so:

                        //options.Cookie.Path = $"/{tenantInfo.Identifier}";
                        options.Cookie.Name = $"{tenantInfo.Id}_authentication";
                        options.LoginPath = $"/{tenantInfo.Identifier}/{options.LoginPath}";
                        options.LogoutPath = $"/{tenantInfo.Identifier}/{options.LogoutPath}";
akasoggybunz commented 4 years ago

Yeah this is much better behavior. Thank you