But after that would it still not go in OnGetAsync inside this if https://github.com/abpframework/abp/blob/8.2.1/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs#L67 or is it needed?
And when some tenant is using AzureAd we don't want to let users to self register locally. So would this be wrong? "Here is the logic: you have to enable local login and self-register both."
Can you confirm that our goal is to get here https://github.com/abpframework/abp/blob/8.2.1/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs#L71
I'm trying to figure out logic here. Why this is returning true and not false? https://github.com/abpframework/abp/blob/8.2.1/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs#L206 Or is those if statements inside CheckSelfRegistrationAsync in wrong order in my perspective?
Here is example url from my situation: https://localhost:44369/Account/Register?isExternalLogin=True&externalLoginAuthSchema=AzureOpenId&returnUrl=%2Fconnect%2Fauthorize%3Fresponse_type%3Dcode%26client_id%3DSCM_App%26state%3DZDhHbYXFDdU9z;%25252Fdashboard%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A4200%252F%26scope%3Dopenid%2520offline_access%2520SCM%26code_challenge%3D7r1ScRaYEcFGjQpvYro4XE_s-zDYdAl_lOO9U2B9_Go%26code_challenge_method%3DS256%26nonce%3DZDhHb2XFDdU9z%26culture%3Den%26ui-culture%3Den%26returnUrl%3D%252Fdashboard
I found it and it almost work. Now my settings for that spesific tenant looks like this.
But after I change tenant it automatically directs to azure AD and after that back to registration page. As it should but now in this scenario Registration button is disabled eventhough it is clearly enabled in settings. What might be wrong now?
So if we enable self registration users can register by them self locally(i.e. without AzureAD)? I started think that maybe this page where user can confirm information is ok if users cannot register locally without azureAD if AzureAD is enabled for them. So is there tenant spesific setting to disable local registration?
But if we don't want to enable self registration? You said earlier that it will automatically register user after external login? I looked at your code and find this property. Where this value is coming from? https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs#L35
I did find away to get it working. Here is what I need to add.
var configRetriever = new OpenIdConnectConfigurationRetriever();
redirectContext.Options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
redirectContext.Options.MetadataAddress,
configRetriever
);
But it returned me back to registration page even you said that "ABP will automatically register external users". Is there some setting to set to get that automatic functionality to work?
Thanks I get further. Here is new code:
authenticationBuilder.AddOpenIdConnect("AzureOpenId", "Azure AD", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.CallbackPath = "/signin-azuread-oidc";
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
EfCoreSaasTenantIdentityProviderSettingRepository saasTenantIdentityProviderSettingsService =
context.Services.GetRequiredService<EfCoreSaasTenantIdentityProviderSettingRepository>();
options.Events.OnRedirectToIdentityProvider = async redirectContext =>
{
var azureAdSettings =
await saasTenantIdentityProviderSettingsService.GetIdentityProviderSettings(IdentityProviderType.ENTRA_ID);
var deserializedSettings = azureAdSettings?.Setting != null
? JsonConvert.DeserializeObject<AzureEntraIdSetting>(azureAdSettings?.Setting)
: null;
if (azureAdSettings != null && deserializedSettings.IsEnabled)
{
redirectContext.ProtocolMessage.ClientId = deserializedSettings.ClientId;
redirectContext.ProtocolMessage.ClientSecret = deserializedSettings.ClientSecret;
redirectContext.ProtocolMessage.IssuerAddress =
redirectContext.ProtocolMessage.IssuerAddress.Replace(configuration["AzureAd:TenantId"],
deserializedSettings.AzureTenantId);
redirectContext.Options.ClientId = deserializedSettings.ClientId;
redirectContext.Options.ClientSecret = deserializedSettings.ClientSecret;
redirectContext.Options.Authority = redirectContext.Options.Authority.Replace(configuration["AzureAd:TenantId"],
deserializedSettings.AzureTenantId);
redirectContext.Options.MetadataAddress = redirectContext.Options.MetadataAddress.Replace(
configuration["AzureAd:TenantId"],
deserializedSettings.AzureTenantId);
redirectContext.Options.TokenValidationParameters
= new TokenValidationParameters()
{
ValidAudiences = new[] { deserializedSettings.ClientId },
ValidIssuers = new[]
{ redirectContext.Options.Authority.TrimEnd('/') }
};
}
};
});
And here is new result:
OpenIdConnectProtocolException: Message contains error: 'invalid_grant', error_description: 'AADSTS700005: Provided Authorization Code is intended to use against other tenant, thus rejected. Trace ID: 4f9f68a2-47dc-4e26-9f26-20a03abf5d00 Correlation ID: 1b736868-e6ee-463a-8169-c074e8ad94b4 Timestamp: 2025-04-10 09:28:58Z', error_uri: 'https://login.microsoftonline.com/error?code=700005'.
Now I managed to get login to work but now token validation fails. It seems that it validates with old ClientId value and token contains updated clientId as it should. Here is error message what I received.
SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: 'b7de466e-7846-45c4-bc91-099f3c89fdff'. Did not match: validationParameters.ValidAudience: '275a3f7d-b568-4865-92a5-6bf5df627e46' or validationParameters.ValidAudiences: 'null'.
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.ValidateTokenUsingHandlerAsync(string idToken, AuthenticationProperties properties, TokenValidationParameters validationParameters)
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync()
Show raw exception details
AuthenticationFailureException: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Volo.Abp.AspNetCore.Tracing.AbpCorrelationIdMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
And here is current solution what works this much.
authenticationBuilder.AddOpenIdConnect("AzureOpenId", "Azure AD", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.CallbackPath = "/signin-azuread-oidc";
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
EfCoreSaasTenantIdentityProviderSettingRepository saasTenantIdentityProviderSettingsService =
context.Services.GetRequiredService<EfCoreSaasTenantIdentityProviderSettingRepository>();
options.Events.OnTokenResponseReceived = async receivedContext =>
{
await Task.FromResult("");
};
options.Events.OnRedirectToIdentityProvider = async redirectContext =>
{
var azureAdSettings =
await saasTenantIdentityProviderSettingsService.GetIdentityProviderSettings(IdentityProviderType.ENTRA_ID);
var deserializedSettings = azureAdSettings?.Setting != null
? JsonConvert.DeserializeObject<AzureEntraIdSetting>(azureAdSettings?.Setting)
: null;
if (azureAdSettings != null && deserializedSettings.IsEnabled)
{
redirectContext.ProtocolMessage.ClientId = deserializedSettings.ClientId;
redirectContext.ProtocolMessage.ClientSecret = deserializedSettings.ClientSecret;
redirectContext.ProtocolMessage.IssuerAddress =
redirectContext.ProtocolMessage.IssuerAddress.Replace(configuration["AzureAd:TenantId"],
deserializedSettings.AzureTenantId);
redirectContext.Options.ClientId = deserializedSettings.ClientId;
redirectContext.Options.ClientSecret = deserializedSettings.ClientSecret;
redirectContext.Options.Authority = redirectContext.Options.Authority.Replace(configuration["AzureAd:TenantId"],
deserializedSettings.AzureTenantId);
redirectContext.Options.MetadataAddress = redirectContext.Options.MetadataAddress.Replace(
configuration["AzureAd:TenantId"],
deserializedSettings.AzureTenantId);
}
};
});
Should I implement some other Event listener for token validation? I tried to implement OnTokenResponseReceived but exception occures before it goes there.
I only get login to work when I set this setting to None. It doesn't feel right thing to do. But is there any other way? options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
Is there possibility to get login working without enabling self-registration when using Azure Entra Id?
After talking with my team enabling registration is not good for us. So is there possibility to get it working like making "registration" automatically if user is logged in through Azure Entra Id?
After I found correct place to enable self registration I managed to get login working with static appsettings.json settings . So now we need to get dynamic config working.