I have a multi-tenant (single database) application which allows for same username/email across different tenants.
At the time of login (Implicit flow) how can I iden
The approach suggested by McGuire will work with OpenIddict (you can access the acr_values property via OpenIdConnectRequest.AcrValues) but it's not the recommended option (it's not ideal from a security perspective: since the issuer is the same for all the tenants, they end up sharing the same signing keys).
Instead, consider running an issuer per tenant. For that, you have at least 2 options:
Give OrchardCore's OpenID module a try: it's based on OpenIddict and natively supports multi-tenancy. It's still in beta but it's actively developed.
Override the options monitor used by OpenIddict to use per-tenant options.
Here's a simplified example of the second option, using a custom monitor and path-based tenant resolution:
public class TenantProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantProvider(IHttpContextAccessor httpContextAccessor)
=> _httpContextAccessor = httpContextAccessor;
public string GetCurrentTenant()
{
// This sample uses the path base as the tenant.
// You can replace that by your own logic.
string tenant = _httpContextAccessor.HttpContext.Request.PathBase;
if (string.IsNullOrEmpty(tenant))
{
tenant = "default";
}
return tenant;
}
}
public void Configure(IApplicationBuilder app)
{
app.Use(next => context =>
{
// This snippet uses a hardcoded resolution logic.
// In a real world app, you'd want to customize that.
if (context.Request.Path.StartsWithSegments("/fabrikam", out PathString path))
{
context.Request.PathBase = "/fabrikam";
context.Request.Path = path;
}
return next(context);
});
app.UseAuthentication();
app.UseMvc();
}
IOptionsMonitor:public class OpenIddictServerOptionsProvider : IOptionsMonitor
{
private readonly ConcurrentDictionary<(string name, string tenant), Lazy> _cache;
private readonly IOptionsFactory _optionsFactory;
private readonly TenantProvider _tenantProvider;
public OpenIddictServerOptionsProvider(
IOptionsFactory optionsFactory,
TenantProvider tenantProvider)
{
_cache = new ConcurrentDictionary<(string, string), Lazy>();
_optionsFactory = optionsFactory;
_tenantProvider = tenantProvider;
}
public OpenIddictServerOptions CurrentValue => Get(Options.DefaultName);
public OpenIddictServerOptions Get(string name)
{
var tenant = _tenantProvider.GetCurrentTenant();
Lazy Create() => new Lazy(() => _optionsFactory.Create(name));
return _cache.GetOrAdd((name, tenant), _ => Create()).Value;
}
public IDisposable OnChange(Action listener) => null;
}
IConfigureNamedOptions:public class OpenIddictServerOptionsInitializer : IConfigureNamedOptions
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly TenantProvider _tenantProvider;
public OpenIddictServerOptionsInitializer(
IDataProtectionProvider dataProtectionProvider,
TenantProvider tenantProvider)
{
_dataProtectionProvider = dataProtectionProvider;
_tenantProvider = tenantProvider;
}
public void Configure(string name, OpenIddictServerOptions options) => Configure(options);
public void Configure(OpenIddictServerOptions options)
{
var tenant = _tenantProvider.GetCurrentTenant();
// Create a tenant-specific data protection provider to ensure authorization codes,
// access tokens and refresh tokens can't be read/decrypted by the other tenants.
options.DataProtectionProvider = _dataProtectionProvider.CreateProtector(tenant);
// Other tenant-specific options can be registered here.
}
}
public void ConfigureServices(IServiceCollection services)
{
// ...
// Register the OpenIddict services.
services.AddOpenIddict()
.AddCore(options =>
{
// Register the Entity Framework stores.
options.UseEntityFrameworkCore()
.UseDbContext();
})
.AddServer(options =>
{
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.UseMvc();
// Note: the following options are registered globally and will be applicable
// to all the tenants. They can be overridden from OpenIddictServerOptionsInitializer.
options.AllowAuthorizationCodeFlow();
options.EnableAuthorizationEndpoint("/connect/authorize")
.EnableTokenEndpoint("/connect/token");
options.DisableHttpsRequirement();
});
services.AddSingleton();
services.AddSingleton, OpenIddictServerOptionsProvider>();
services.AddSingleton, OpenIddictServerOptionsInitializer>();
}
To confirm this works correctly, navigate to http://localhost:[port]/fabrikam/.well-known/openid-configuration (you should get a JSON response with the OpenID Connect metadata).