- ABP Framework version: v8.2.0
- UI Type: Blazor Server
- Database System: EF Core
- Tiered (for MVC) or Auth Server Separated (for Angular): yes
Hi,
We have integrated Azure AD authentication in our application. However, when we try to logout, it does not logout from Azure AD. Can you please guide us on how to implement logout from external provider in abp?
29 Answer(s)
-
0
-
0
Hi,
What we are looking for is to logout from the external provider, by invoking the end session endpoint, and perform a single sign out. We have registered Azure AD as external provider in the auth server using OpenID Connect with dynamic options. So, when we logout we want to redirect to Azure AD logout uri.
-
0
Hi,
After my check, This is the default behavior.
If you want to logout from Azure Id, You need to redirect manually.
For example:
[Dependency(ReplaceServices = true)] [ExposeServices(typeof(LogoutModel))] public class MyLoginOutModel : LogoutModel { public override async Task<IActionResult> OnGetAsync() { if (CurrentUser.IsAuthenticated) { await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = IdentitySecurityLogActionConsts.Logout }); } // await SignInManager.SignOutAsync(); await HttpContext.SignOutAsync(ConfirmUserModel.ConfirmUserScheme); await HttpContext.SignOutAsync(ChangePasswordModel.ChangePasswordScheme); // redirect to azure id return Redirect("https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri=https://localhost:44382/Account/Login"); } }
-
0
-
0
Hi,
It works for me
context.Services.AddAuthentication() .AddOpenIdConnect("AzureOpenId", "Azure AD OpenId", options => { options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/"; options.ClientId = configuration["AzureAd:ClientId"]; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.CallbackPath = configuration["AzureAd:CallbackPath"]; options.ClientSecret = configuration["AzureAd:ClientSecret"]; options.RequireHttpsMetadata = false; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("email"); options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); });
[Dependency(ReplaceServices = true)] [ExposeServices(typeof(LogoutModel))] public class MyLoginOutModel : LogoutModel { public override async Task<IActionResult> OnGetAsync() { if (CurrentUser.IsAuthenticated) { await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = IdentitySecurityLogActionConsts.Logout }); } // await SignInManager.SignOutAsync(); await HttpContext.SignOutAsync(ConfirmUserModel.ConfirmUserScheme); await HttpContext.SignOutAsync(ChangePasswordModel.ChangePasswordScheme); return SignOut("AzureOpenId"); } }
-
0
-
0
The following is our configuration. Added client credentials as dynamic options, so that each tenant can configure their own credentials.
OnGetAsync of logout model is not even getting executed. Is there anything else I have to do to make this work? Is the configuration added in Auth Server in the sample you provided?
-
0
Is the configuration added in Auth Server in the sample you provided?
No, i didn't do any configuration else. that's all
you can share a simple example with me, i will check it.
my email is shiwei.liang@volosoft.com
-
0
Hi,
I have just added the configuration as mentioned in the document.
https://docs.abp.io/en/commercial/latest/modules/account#ipostconfigureaccountexternalprovideroptions
I have also added dynamic options configuration in the identity service. Login is working perfectly. When I checked the AbpAccountAuthenticationRequestHandler I couldn't find any handling for Signout.
-
0
you can share a simple example with me, i will check it.
-
0
Hi,
Is there any update on this?
-
0
Hi,
I can confirm the problem, we will fix it in the next version.
You can try:
public class MyAbpAccountAuthenticationRequestHandler<TOptions, THandler> : IAuthenticationRequestHandler, IAuthenticationSignOutHandler where TOptions : RemoteAuthenticationOptions, new() where THandler : RemoteAuthenticationHandler<TOptions> { protected readonly THandler InnerHandler; protected readonly IOptions<TOptions> OptionsManager; public MyAbpAccountAuthenticationRequestHandler(THandler innerHandler, IOptions<TOptions> optionsManager) { InnerHandler = innerHandler; OptionsManager = optionsManager; } public virtual async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) { await InnerHandler.InitializeAsync(scheme, context); } public virtual async Task<AuthenticateResult> AuthenticateAsync() { return await InnerHandler.AuthenticateAsync(); } public virtual async Task ChallengeAsync(AuthenticationProperties properties) { await OptionsManager.SetAsync(InnerHandler.Scheme.Name); ObjectHelper.TrySetProperty(InnerHandler, handler => handler.Options, () => OptionsManager.Value); await InnerHandler.ChallengeAsync(properties); } public virtual async Task ForbidAsync(AuthenticationProperties properties) { await InnerHandler.ForbidAsync(properties); } public async Task SignOutAsync(AuthenticationProperties properties) { await OptionsManager.SetAsync(InnerHandler.Scheme.Name); ObjectHelper.TrySetProperty(InnerHandler, handler => handler.Options, () => OptionsManager.Value); var signOutHandler = InnerHandler as IAuthenticationSignOutHandler; if (signOutHandler == null) { throw new InvalidOperationException($"The authentication handler registered for scheme '{InnerHandler.Scheme}' is '{InnerHandler.GetType().Name}' which cannot be used for SignOutAsync"); } await signOutHandler.SignOutAsync(properties); } public virtual async Task<bool> HandleRequestAsync() { if (await InnerHandler.ShouldHandleRequestAsync()) { await OptionsManager.SetAsync(InnerHandler.Scheme.Name); ObjectHelper.TrySetProperty(InnerHandler, handler => handler.Options, () => OptionsManager.Value); } return await InnerHandler.HandleRequestAsync(); } public virtual THandler GetHandler() { return InnerHandler; } } public static class AuthenticationBuilderExtensions { public static AuthenticationBuilder AddMyAbpAccountDynamicOptions<TOptions, THandler>(this AuthenticationBuilder authenticationBuilder) where TOptions : RemoteAuthenticationOptions, new() where THandler : RemoteAuthenticationHandler<TOptions> { authenticationBuilder.Services.AddScoped(typeof(AccountExternalProviderOptionsManager<TOptions>)); authenticationBuilder.Services.Replace(ServiceDescriptor.Scoped(typeof(IOptions<TOptions>), provider => provider.GetRequiredService<AccountExternalProviderOptionsManager<TOptions>>())); authenticationBuilder.Services.Replace(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<TOptions>), provider => provider.GetRequiredService<AccountExternalProviderOptionsManager<TOptions>>())); authenticationBuilder.Services.Replace(ServiceDescriptor.Scoped(typeof(IOptionsMonitor<TOptions>), provider => provider.GetRequiredService<AccountExternalProviderOptionsManager<TOptions>>())); authenticationBuilder.Services.Replace(ServiceDescriptor.Transient<IOptionsFactory<TOptions>, AccountExternalProviderOptionsFactory<TOptions>>()); var handler = authenticationBuilder.Services.LastOrDefault(x => x.ServiceType == typeof(THandler)); authenticationBuilder.Services.Replace(new ServiceDescriptor( typeof(THandler), provider => new MyAbpAccountAuthenticationRequestHandler<TOptions, THandler>( (THandler)ActivatorUtilities.CreateInstance(provider, typeof(THandler)), provider.GetRequiredService<IOptions<TOptions>>()), handler?.Lifetime ?? ServiceLifetime.Transient)); return authenticationBuilder; } } context.Services.AddAuthentication() .AddOpenIdConnect("AzureAD", "Azure AD", options => { options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.RequireHttpsMetadata = false; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("email"); options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); options.CallbackPath = configuration["AzureAd:CallbackPath"]; }) .AddMyAbpAccountDynamicOptions<OpenIdConnectOptions, OpenIdConnectHandler>() .Services.AddDynamicExternalLoginProviderOptions<OpenIdConnectOptions>("AzureAD", options => { options.WithProperty(x => x.Authority); options.WithProperty(x => x.ClientId); options.WithProperty(x => x.ClientSecret, isSecret: true); });
[Route("connect/logout")] [ApiExplorerSettings(IgnoreApi = true)] [Dependency(ReplaceServices = true)] [ExposeServices(typeof(LogoutController))] public class MyLogoutController : LogoutController { [HttpGet] public override async Task<IActionResult> GetAsync() { await SignInManager.SignOutAsync(); return SignOut(authenticationSchemes: "AzureAD"); } }
-
0
your ticket was refunded
-
0
HI, liangshiwei is this fix also valid for Blazor WASM?
-
0
yes
-
0
Hi, The logout is working, but it is redirecting back to the auth server login page. How can we invoke the post logout uri of the client application?
-
0
You can add the parameters post logout uri manually
-
0
-
0
get
redirect uri
from HttpContext.Request and add to AuthenticationProperties.RedirectUri -
0
-
0
You can try this ,it works for me:
[Route("connect/logout")] [ApiExplorerSettings(IgnoreApi = true)] [Dependency(ReplaceServices = true)] [ExposeServices(typeof(LogoutController))] public class MyLogoutController : LogoutController { [HttpGet] public override async Task<IActionResult> GetAsync() { await SignInManager.SignOutAsync(); return SignOut(authenticationSchemes: "AzureAD" , properties: new AuthenticationProperties() { RedirectUri = "https://localhost:44314" // client URL }); } }
-
0
Hi liangshiwei, If instead of "https://localhost:44314" you showed how to retrieve the client URL from settings or configuration it would be a better solution
:-)
-
0
Hi,
You can get the client URL from the HTTP Request.
-
0
Can you update your example with the final code we should use?
Thank you
-
0
@liangshiwei The blazor application has initiated an oidc signout flow and the expectation is that auth server will redirect back to the post logout uri with a state param.