I'm developing multi-tenant ASP.NET MVC application using Finbuckle.Multitenant and IdentityServer4 (using standard classes and controllers from their tutorials). My app uses route strategy for tenants (https://host/tenant1/controller/action) and I use separate cookies for each tenant (cookie named auth.tenant1, auth.tenant2... etc.) Everything works fine unless I specify custom Path for auth cookie. If all of them has Path=/ everything is ok. BUT when I set Path=/tenant1 to cookie named auth.tenant1 and the same pattern for every other tenants I have a circular redirects after passing consent screen. When I click "yes" on consent screen I got 302 challenge redirect from IdentityServer middleware on client side. It redirects me back to Consent screen. After each "yes" I'm returning to consent. However it happens only during authentication process. If I open new tab and head to https://host/tenant1 I won't be redirected and will be successfully authenticated. Googled for answers for days but haven't found any solutions. Please help me!
Here is my client's Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddMultiTenant().WithInMemoryStore(Configuration.GetSection("MultiTenant:InMemoryStore"))
.WithRouteStrategy(MapRoutes)
.WithRemoteAuthentication()
.WithPerTenantOptions<AuthenticationOptions>((options, tenantContext) =>
{
// Allow each tenant to have a different default challenge scheme.
if (tenantContext.Items.TryGetValue("ChallengeScheme", out object challengeScheme))
{
options.DefaultChallengeScheme = (string)challengeScheme;
}
})
.WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantContext) =>
{
options.Cookie.Name += tenantContext.Identifier;
options.Cookie.Path = "/" + tenantContext.Identifier;
options.LoginPath = "/" + tenantContext.Identifier + "/Home/Login";
});
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>
{
o.Cookie.Name = "auth.";
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:5000";
options.RequireHttpsMetadata = true;
options.ClientId = "mvc";
options.SaveTokens = true;
options.ClientSecret = "secret";
//Hybrid protocols (OpenId + OAuth)
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.GetClaimsFromUserInfoEndpoint = true;
//ask to allow access to testApi
options.Scope.Add("testApi");
//allows requesting refresh tokens for long lived API access
options.Scope.Add("offline_access");
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = ctx =>
{
var tenant = ctx.HttpContext.GetMultiTenantContext()?.TenantInfo?.Identifier;
ctx.ProtocolMessage.AcrValues = $"tenant:{tenant}";
return Task.FromResult(0);
}
};
});
}
private void MapRoutes(IRouteBuilder router)
{
router.MapRoute("Default", "{__tenant__=tenant1}/{controller=Home}/{action=Index}/{id?}");
}
Here is my client configuration on IdentityServer's side (appsettings.json):
{
"clientId": "mvc",
"clientName": "MVC Client",
"allowedGrantTypes": [ "hybrid", "client_credentials" ],
"clientSecrets": [
{ "value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } // Sha256("secret")
],
"redirectUris": [ "https://localhost:5002/signin-oidc" ],
"postLogoutRedirectUris": [ "https://localhost:5002/signout-callback-oidc" ],
"allowedScopes": [ "openid", "profile", "testApi" ],
"allowOfflineAccess": true
},
My IdentityServer4 config:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var identityServerBuilder = services.AddIdentityServer()
.AddDeveloperSigningCredential();
if (_config.GetSection("AppSettings:UseDummyAuthentication").Get<bool>())
{
identityServerBuilder
.AddInMemoryIdentityResources(_config.GetSection("IdentityResources"))
.AddInMemoryApiResources(_config.GetSection("ApiResources"))
.AddInMemoryClients(_config.GetSection("Clients"))
.AddTestUsers(_config.GetSection("TestUsers"));
}
services.AddAuthentication();
}
Please help me, guys! How can I make Path=/tenant1 scheme work without redirects on consent screen???