Closed marceloataide closed 2 months ago
Hi, the OpenIdConnect authentication will always validate the options. Even though you are doing per-tenant options the base options need to satisfy validation.
Take a look at this older sample, you'll see I set dummy values knowing they will be overridden:
I should add something to the docs about this. Cheers.
I dont know how logon on keycloak server like this image
The OpenID connect authority for the tenant will send them there. Did you try a “dummy” default value for the OpenID connect settings so that you wouldn’t get the validation message?
I dont know whats is wrong.. always send to https://localhost:7208/Account/Login
using Finbuckle.MultiTenant; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using IdentitySample.Data; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using System.Security.Claims; using IdentitySample;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddDbContext
builder.Services.AddDefaultIdentity
// Add MultiTenant
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddOpenIdConnect(options => { options.ClientId = " tenant "; // Needed for validation, will be overwritten per-tenant. options.Authority = " tenant "; // Needed for validation, will be overwritten per-tenant. options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; //Keycloak server options.ClientSecret = " tenant "; options.MetadataAddress = " tenant "; //// //Require keycloak to use SSL options.RequireHttpsMetadata = false; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("openid"); options.Scope.Add("profile"); //Save the token options.SaveTokens = true; //Token response type, will sometimes need to be changed to IdToken, depending on config. options.ResponseType = OpenIdConnectResponseType.Code; //SameSite is needed for Chrome/Firefox, as they will give http error 500 back, if not set to unspecified. options.NonceCookie.SameSite = SameSiteMode.Unspecified; options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = ClaimTypes.Role,
ValidateIssuer = true
};
}) .AddCookie(cookie => { //Sets the cookie name and maxage, so the cookie is invalidated. cookie.Cookie.Name = "keycloak.cookie"; cookie.Cookie.MaxAge = TimeSpan.FromMinutes(60); cookie.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; cookie.SlidingExpiration = true;
});
builder.Services.AddMultiTenant
var app = builder.Build();
// Apply migrations if needed
var store = app.Services.GetRequiredService<IMultiTenantStore
// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); }
app.UseHttpsRedirection(); app.UseStaticFiles();
app.UseMultiTenant(); app.UseRouting();
app.UseAuthentication(); app.UseAuthorization();
app.MapRazorPages();
app.UseEndpoints(endpoints => { endpoints.MapControllerRoute("default", "{tenant}/{controller=Home}/{action=Index}"); });
app.Run();
I don’t see anything obvious. It’s going to the login url because in your AddAuthentication you set the default scheme to cookies so any unauthorized request will redirect via cookie settings.
I recommend you try to get it working WITHOUT multitenant just to make sure these problems are not due to multitenant. Once you get it working normally see if you still see this behavior when you add multitenant.
Andrew with your tips worked well. I would like share this solution whats worked well with keycloak. I am testing yet, but now I can logon on the server. Thanks for your help
using Finbuckle.MultiTenant; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using IdentitySample.Data; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using System.Security.Claims; using IdentitySample; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddDbContext
builder.Services.AddDefaultIdentity
.AddCookie(cookie => { //Sets the cookie name and maxage, so the cookie is invalidated. cookie.Cookie.Name = "keycloak.cookie"; cookie.Cookie.MaxAge = TimeSpan.FromMinutes(60); cookie.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; cookie.SlidingExpiration = true;
})
.AddOpenIdConnect(options =>
{
//Use default signin scheme
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//Keycloak server
options.Authority = "tenant";
//Keycloak client ID
options.ClientId = "tenant";
//Keycloak client secret
options.ClientSecret = "tenant";
//Keycloak .wellknown config origin to fetch config
// options.MetadataAddress = "tenant";
////Require keycloak to use SSL
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
//Save the token
options.SaveTokens = true;
//Token response type, will sometimes need to be changed to IdToken, depending on config.
options.ResponseType = OpenIdConnectResponseType.Code;
//SameSite is needed for Chrome/Firefox, as they will give http error 500 back, if not set to unspecified.
options.NonceCookie.SameSite = SameSiteMode.Unspecified;
options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = ClaimTypes.Role,
ValidateIssuer = true
};
});
//builder.Services.AddAuthorization(options => //{ // //Create policy with more than one claim // options.AddPolicy("users", policy => // policy.RequireAssertion(context => // context.User.HasClaim(c => // (c.Value == "user") || (c.Value == "admin")))); // //Create policy with only one claim // options.AddPolicy("admins", policy => // policy.RequireClaim(ClaimTypes.Role, "admin")); // //Create a policy with a claim that doesn't exist or you are unauthorized to // options.AddPolicy("noaccess", policy => // policy.RequireClaim(ClaimTypes.Role, "noaccess")); //});
builder.Services.AddMultiTenant
var app = builder.Build();
var store = app.Services.GetRequiredService<IMultiTenantStore
// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); }
app.UseHttpsRedirection(); app.UseStaticFiles();
app.UseMultiTenant(); app.UseRouting();
app.UseAuthentication(); app.UseAuthorization();
app.MapRazorPages();
app.UseEndpoints(endpoints => { endpoints.MapControllerRoute("default", "{tenant}/{controller=Home}/{action=Index}"); });
app.Run();
Hi, I’m glad you got it working and thanks for sharing your solution for others!
I am getting this error
An unhandled exception occurred while processing the request. ArgumentException: Options.ClientId must be provided (Parameter 'ClientId') Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.Validate()
TargetInvocationException: Exception has been thrown by the target of an invocation. System.Reflection.MethodInvoker.Invoke(object obj, IntPtr* args, BindingFlags invokeAttr)
MultiTenantException: Exception in Finbuckle.MultiTenant.Strategies.RemoteAuthenticationCallbackStrategy.GetIdentifierAsync. Finbuckle.MultiTenant.Strategies.MultiTenantStrategyWrapper.GetIdentifierAsync(object context)
I am using dotnet core 7
I will use keycloak with multiple tenant..