DuendeSoftware / Support

Support for Duende Software products
20 stars 0 forks source link

Logout from Azure AD B2C is not redirecting after complete #482

Closed Tyler-V closed 1 year ago

Tyler-V commented 1 year ago

Which version of Duende IdentityServer are you using?

    <PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="6.2.3" />
    <PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.2.3" />

Which version of .NET are you using? .NET 6

Describe the bug We are using Identity Server with AspNetIdentity with no local login and supporting only one IdP, Azure AD B2C

On login, the user is routed through IdentityServer directly to Azure AD to login and is redirected to the frontend (SPA) without issue.

On logout, a request is initiated from the frontend to Identity Server where I am able to debug the /Account/Logout page where the signInManager.SignOutAsync is invoked. The identity provider is observed and then SignOut is returned from the razor page with the RedirectUri configured to /Account/Logout/LoggedOut?logoutId=...

After return SignOut(new AuthenticationProperties { RedirectUri = url }, idp); is called the user is logged out of Azure AD and remains stuck on the https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logoutsession page without redirecting the user back to Identity Server with the logout id ... to then be redirected to the PostLogoutRedirectUris set for the Client.

Additional context

OpenID registration

        builder.Services
            .AddAuthentication()
            .AddOpenIdConnect("AAD", "Azure Active Directory", options =>
            {
                options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                options.SignOutScheme = IdentityServerConstants.SignoutScheme;
                options.SaveTokens = true;
                options.ResponseType = "code";

                options.Authority = appSettings.IdentityServer.AzureActiveDirectory.Authority;
                options.MetadataAddress = appSettings.IdentityServer.AzureActiveDirectory.MetadataAddress;
                options.ClientId = appSettings.IdentityServer.AzureActiveDirectory.ClientId;
                options.ClientSecret = appSettings.IdentityServer.AzureActiveDirectory.ClientSecret;

                options.RemoteSignOutPath = "/Account/Logout/LoggedOut";
                options.SignedOutCallbackPath = "/Account/Logout/LoggedOut";
            });

Client

            new Client
            {
                ClientId = "xxx",
                ClientName = "xxx",
                AllowedGrantTypes = GrantTypes.Code,
                RequireClientSecret = false,
                RedirectUris = { "http://localhost:4200" },
                PostLogoutRedirectUris = { "http://localhost:4200" },
                AllowedCorsOrigins = { "http://localhost:4200" },
                AlwaysIncludeUserClaimsInIdToken = true,
                AllowOfflineAccess = true,
                EnableLocalLogin = false,
                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.Email,
                    IdentityServerConstants.StandardScopes.OfflineAccess,
                    ApiScopes.xxx.Name
                }
            }

From the network tab, first the Identity Server logout is observed

Request URL: https://localhost:44301/Account/Logout?logoutId=CfDJ8Bt8jxpng7RCpoN7k-v0Cn3FmBsTlAIUONiO_NzK_hykiaVRWlcwS9ThF69kWO5WlPI2eMQEIdFIgasAH3phu9XOGo2QLq0QEfG28lhRki9QJ7-LWZPXqksu2GdI-p34piiir1zG6RBYqLUigDXWfQrmUxsdVZWtV5XeatYqhIptmvFrZNdCpiAzZ_oSHdQRmu_qEKcWUFCiFm0lT3DD46x0AkBkSv0jGVVgcL2KXtf2iM9ifhGRy7hv-p3INErG5EIwMcjWqom3o4Probixv0-Zmw6FLPLx-VqpfLstzzuwpXz8W6edDn9grr0Bnt-wjtcYeFnKeeXxSaGqEzMje_Hg7MoS_dAV9wy9J_yTjxcMCIeeHUn7oKGuz7HbROhr4TNgQ36DKrzEaNyVvtF3LgpSiV3CwcZiajJGIXmmKGts

IdentityServer/Pages/Account/Logout/Index.cshtml image image

It is then followed by the logout to Azure AD where the post_logout_redirect_uri is present

Request URL: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logout?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44301%2FAccount%2FLogout%2FLoggedOut&state=CfDJ8Bt8jxpng7RCpoN7k-v0Cn2tEWA-ufSzW0aJ3N94StIsyYbitkr7teuN1ZSjzrFcbOvXQB8CVOTr5YnOHNMBCVTi3gBIcDKHtZkfsbUDiBllyur6q6ZcuQX7JeFjeRHrIPXg0EMbwL_7JBtgUWcSY3kgxO51uaOm_abj-S8wvc8EQd7w2E4ANxgFViXmRab2ed1tfIcTdejORTSqrrvw9XxoxuOfDjm_ZD5JMGiw-a8ZE3YmnHxsYyq0DKM2SfCDP-5ric4WShLPEPvLLgr3DzI2YZxTGlbkKxZDmJ8bXUFr2crG1aITlUfkQGzXfFE800W-usLl348mQ9IUgUBOjGUxMn9HXDFtze3RHPRWSxgI8lRiGdWckvKy3QTR49FFvnOiif6ZEYw-vOl97SFY4uhiKHvXwwbF_615TlgijQq5KK28a60khG63neQRHinf9K_37n8fxrhGcj_kGsIyfVyp79S1Nps8VmgmMJFJAzAdPl7E3KcmRV2bHMMzEiW6-qX8zqsmFmug3mFAsY3PO7VXCGqNdL4ALy5cciqT-XlgfQbLUABK-NZ9j941E1dhhdTfwVxLMxpsiCkO0NNN4l3csipiBAp3wRvPGfPA4NvAC8GhigINPWDnu3pSArV2JJlT1k9dM7LrnEcIWNLQ-bAyokRrK4KY5UJDAIg_np_2HkQuKC1lEkN7qxAOZxJkF_yqwblGYqUa_aOBcNYRUc9DQesDYpWczcU466Ml_ZKOM8yBso6Q1dY48Ufzc63glw&x-client-SKU=ID_NET6_0&x-client-ver=6.25.1.0

We are then stuck at this page without redirection image

The redirect to /Account/Logout/LoggedOut never happens - which if it did redirect, I can see it is correctly finding the PostLogoutRedirectUri set in the Client from the interactionService

image

It appears that this redirect should be allowed, any insights would be helpful

Tyler-V commented 1 year ago

Also worth mentioning,

Without setting options in .AddOpenIdConnect

                options.RemoteSignOutPath = "/Account/Logout/LoggedOut";
                options.SignedOutCallbackPath = "/Account/Logout/LoggedOut";

The logout initiated on Azure is https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logout?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44301%2Fsignout-callback-oidc

Which also does not work to redirect after logout

brockallen commented 1 year ago

Hi @Tyler-V. I don't know the current state of affairs with AB2C, but can you get it all working with a stand alone ASP.NET Core app and AB2C the way you want without IdentityServer? In other words, let's ensure the entire connection to AB2C is all correct standalone. If so, then we can help you in IdentityServer if things don't work after that.

josephdecock commented 1 year ago

I agree with Brock that it is a good idea to make sure that you can rule out misconfiguration at an external provider the way that he describes. One thing that I did notice though, is that your redirect to the logout endpoint lacks an id_token_hint:

Whitespace added for legibility:

https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logout?
post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44301%2FAccount%2FLogout%2FLoggedOut
&state=CfDJ8Bt8jxpng7RCpoN7k-v0Cn2tEWA-ufSzW0aJ3N94StIsyYbitkr7teuN1ZSjzrFcbOvXQB8CVOTr5YnOHNMBCVTi3gBIcDKHtZkfsbUDiBllyur6q6ZcuQX7JeFjeRHrIPXg0EMbwL_7JBtgUWcSY3kgxO51uaOm_abj-S8wvc8EQd7w2E4ANxgFViXmRab2ed1tfIcTdejORTSqrrvw9XxoxuOfDjm_ZD5JMGiw-a8ZE3YmnHxsYyq0DKM2SfCDP-5ric4WShLPEPvLLgr3DzI2YZxTGlbkKxZDmJ8bXUFr2crG1aITlUfkQGzXfFE800W-usLl348mQ9IUgUBOjGUxMn9HXDFtze3RHPRWSxgI8lRiGdWckvKy3QTR49FFvnOiif6ZEYw-vOl97SFY4uhiKHvXwwbF_615TlgijQq5KK28a60khG63neQRHinf9K_37n8fxrhGcj_kGsIyfVyp79S1Nps8VmgmMJFJAzAdPl7E3KcmRV2bHMMzEiW6-qX8zqsmFmug3mFAsY3PO7VXCGqNdL4ALy5cciqT-XlgfQbLUABK-NZ9j941E1dhhdTfwVxLMxpsiCkO0NNN4l3csipiBAp3wRvPGfPA4NvAC8GhigINPWDnu3pSArV2JJlT1k9dM7LrnEcIWNLQ-bAyokRrK4KY5UJDAIg_np_2HkQuKC1lEkN7qxAOZxJkF_yqwblGYqUa_aOBcNYRUc9DQesDYpWczcU466Ml_ZKOM8yBso6Q1dY48Ufzc63glw
&x-client-SKU=ID_NET6_0
&x-client-ver=6.25.1.0

I don't know what Azure B2C does specifically with their support for OIDC RP-Initiated Logout, but if they're following the spec, you have to send the id_token_hint, otherwise the post logout redirect uri will be ignored.

The OIDC authentication handler will set the id_token_hint for you if you have the id_token saved in the signout scheme.

You have options.SaveTokens = true; so the ExternalCookieAuthenticationScheme should have the id token in it. Typically there's a callback page to move from the external cookie into your main idsvr cookie. I would check that callback page first, and make sure that the id token is being carried forward into your idsvr cookie. Our template UI page has a CaptureExternalLoginContext that does that. Check that yours does as well, and debug it during the login process to make sure you are saving the id token.

Tyler-V commented 1 year ago

So some additional insights thanks for your feedback @brockallen @josephdecock

I can confirm that AddOpenIdConnect using the provided authority in the endpoints from Azure AD works for sign-in and sign-out without issue...

Using Azure B2C is a little different according to the quickstart docs, requiring AddMicrosoftIdentityWebApp and specifying custom policies / sign-up flows to be set (if using them). I created some basic custom policies and was able to get the login page to appear but signing in threw various exceptions in Identity Server.

If I try it without custom policies and just use the Azure AD OpenID Connect metadata document listed in the endpoints as my authority, login works but logout (with redirect) does not.

image

I have read that the redirect_uri may not work for security reasons without including id_token_hint for security reasons, im not sure how I would go about appending that to the request that is made to logout to Microsoft if it is not including it currently

https://learn.microsoft.com/en-us/aspnet/core/security/authentication/azure-ad-b2c?view=aspnetcore-7.0 https://learn.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-application?tabs=visual-studio

josephdecock commented 1 year ago

I have read that the redirect_uri may not work for security reasons without including id_token_hint for security reasons

That's required behavior in the OIDC spec.

When you call AddMicrosoftIdentityWebApp, it sets up the OIDC handler behind the scenes, customized for Azure B2C. I don't know all the details of B2C, but the regular OIDC handler sets the id_token_hint when you call SignOutAsync if the id token is saved in the session's metadata (that is, the AuthenticationProperties in your session cookie). That will happen if you set SaveTokens = true on the OIDC handler. Again, I'm not sure if B2C properly supports this, so I would check into that and also make sure that you have SaveTokens enabled and are actually saving the id token into the auth properties.

brockallen commented 1 year ago

All set on this issue -- can we close?

Tyler-V commented 1 year ago

Hey no luck on using Azure AD B2C unfortunately I have exhausted all options on my side, and was hoping this would play nicely out of the box but B2C appears to be a different service offering and I don't see many examples to work off of on how to integrate it into Identity Server - thanks for trying to help