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.26k stars 261 forks source link

Claim strategy with OAuth / OpenIddict #646

Open InFarAday opened 1 year ago

InFarAday commented 1 year ago

I'm writing my own authorization server both as a way to learn, and as a way to POC our potential future SSO solution for all our in-house apps at the company where I work. I'm using OpenIddict, along with your multi-tenant solution and this has been fantastic so far, both are very well designed.

I'm set on a multi-tenant architecture using a db per tenant ; meta-tenant information is registered in a special database called "tenants" (using PostgreSQL). What happens is that before the user is logged in, the tenant is null and the login form contains the field "client id" as well as username & password. When the user submits the form, I have set a delegate strategy to read the tenant information from the form data and I can then login the user against its tenant database. At any point the tenancy works with dependency injection by injecting the tenant’s connection string in the scoped DbContext instance. Thereafter his client id (tenant id) is stored as a claim ; at this point in a cookie, and the claim strategy takes over.

The step 2 happens where the user is directed towards /authorize to retrieve his code. This works well, since the authentication scheme (and the Http User) is still cookie-based. But hell breaks loose when going to /token after the user is signed in using OpenIddict's authentication scheme. What happens is two scenarios:

  1. If UseMultiTenant() is before UseAuthentication(), then it can't retrieve the tenant because the claims aren't set yet
  2. If it is the opposite, UseAuthentication() breaks because it can't authorize the incoming request against the tenant's database (it doesn't know who the tenant is)

I don't know how to get out of this deadlock in a clean way and I would very much appreciate any insight. Moving the tenant resolution out of the claims is not really an option either unfortunately. The two options I see would be either making OpenIddict tenant-aware somehow, or even better make Finbuckle able to resolve the tenant before the actual authentication happens, if possible. I can provide some of the code bits, the part in Program.cs might especially be interesting.

Thank you for your time.

InFarAday commented 1 year ago

This issue is vaguely related to this one : https://github.com/finbuckle/Finbuckle.MultiTenant/issues/468 It seems that I was able to fix the first part of the issue with the following:

.WithClaimStrategy(MultiTenancyClaims.ClientId, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)
.WithClaimStrategy(MultiTenancyClaims.ClientId, CookieAuthenticationDefaults.AuthenticationScheme)

But the AuthenticateAsync call in the middleware fails, OpenIddict throws the following error:

InvalidOperationException: An error occurred while retrieving the OpenIddict server context. On ASP.NET Core, this may indicate that the authentication middleware was not registered early enough in the request pipeline. Make sure that 'app.UseAuthentication()' is registered before 'app.UseAuthorization()' and 'app.UseEndpoints()' (or 'app.UseMvc()') and try again.

I guess that at this point this is out of your hands and that I will have to bring this issue to OpenIddict

AndrewTriesToCode commented 1 year ago

Hi, I'm sorry I didn't see this sooner. Did you find a working solution overall?

InFarAday commented 1 year ago

Hi, I took it to OpenIddict (can be found here) but unfortunately I have to abandon claim-based tenancy resolution. This is not what I wanted but it is what it is. I now am getting the solution to work with hostname based tenancy resolution. I have to say that your project and OpenIddict are still wonderful projects, despite this hickup.