Azure AD federated logout not redirecting to client application
Asked Answered
P

2

7

I am using Identity Server 3 for a central authentication server to a .Net MVC web application I am building.

I have configured the authentication server to use the Open ID Connect identity provider in order to allow users to authenticate against a multi-tenant Azure Active Directory account, using the Hybrid flow.

Currently, sign in works as expected with my client application redirecting to the authentication server which in turn redirects to Microsoft for login before returning back to my client application with a correctly populated Access Token.

However, when I try to logout I am redirected to Microsoft correctly, but the page stops when it arrives back at the authentication server, rather than continuing back to my client application.

I believe I have setup the post logout redirect correctly as outlined here and I think all of my settings are ok.

When I pull the Identity Server 3 code down and debug it, it is correctly setting the signOutMessageId onto the query string, but hits the following error inside the UseAutofacMiddleware method when it is trying to redirect to my mapped signoutcallback location:

Exception thrown: 'System.InvalidOperationException' in mscorlib.dll

Additional information: Headers already sent

My Authentication Server setup:

app.Map("identity", idsrvApp => {
    var idSvrFactory = new IdentityServerServiceFactory();

    var options = new IdentityServerOptions
    {                
        SiteName = "Site Name",
        SigningCertificate = <Certificate>,
        Factory = idSvrFactory,
        AuthenticationOptions = new AuthenticationOptions
        {
            IdentityProviders = ConfigureIdentityProviders,
            EnablePostSignOutAutoRedirect = true,
            PostSignOutAutoRedirectDelay = 3
        }
    };
    idsrvApp.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    idsrvApp.UseIdentityServer(options);

    idsrvApp.Map("/signoutcallback", cb => {
                    cb.Run(async ctx => {
                               var state = ctx.Request.Cookies["state"];
                               ctx.Response.Cookies.Append("state", ".", new Microsoft.Owin.CookieOptions { Expires = DateTime.UtcNow.AddYears(-1) });
                               await ctx.Environment.RenderLoggedOutViewAsync(state);
                    });
                });
});

My Open Id Connect setup to connect to Azure AD:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        AuthenticationType = "aad",
        SignInAsAuthenticationType = signInAsType,

        Authority = "https://login.microsoftonline.com/common/",
        ClientId = <Client ID>,
        AuthenticationMode = AuthenticationMode.Active,
        TokenValidationParameters = new TokenValidationParameters
        {
            AuthenticationType = Constants.ExternalAuthenticationType,
            ValidateIssuer = false,
        },
        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            AuthorizationCodeReceived = (context) =>
            {
                var code = context.Code;
                ClientCredential credential = new ClientCredential(<Client ID>, <Client Secret>);
                string tenantId = context.AuthenticationTicket.Identity.FindFirst("tid").Value;
                AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}");
                AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                            code, new Uri(<Identity Server URI>/aad/"), credential, "https://graph.windows.net");

                return Task.FromResult(0);
            },
            RedirectToIdentityProvider = (context) =>
            {
                string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
                context.ProtocolMessage.RedirectUri = appBaseUrl + "/aad/";
                context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl + "/signoutcallback";

                if (context.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
                {
                    var signOutMessageId = context.OwinContext.Environment.GetSignOutMessageId();
                    if (signOutMessageId != null)
                    {
                        context.OwinContext.Response.Cookies.Append("state", signOutMessageId);
                    }
                }
                return Task.FromResult(0);
            }
    });

I cannot find any information about the cause of or solution to this problem. How do I configure this to correctly redirect back to my client application?

Edit:

Related discussion on GitHub: https://github.com/IdentityServer/IdentityServer3/issues/2657

I have also tried this with the latest version of Identity Server on MyGet (v2.4.1-build00452) with the same problem.

I have also created a repository that reproduces the issue for me here: https://github.com/Steve887/IdentityServer-Azure/

My Azure AD setup:

enter image description here

Puckery answered 2/3, 2016 at 1:58 Comment(2)
There was a discussion at - github.com/IdentityServer/IdentityServer3/issues/1000 regarding this feature. I am not sure if this helps you. Did you got anything in the IdServ3 logs ?Defroster
@Defroster I believe that discussion is what led to the implementation of the GetSignOutMessage method in the first place. And no, nothing in the logs that I can see.Puckery
P
0

I believe you were experiencing a bug that is fixed in 2.5 (not yet released as of today): https://github.com/IdentityServer/IdentityServer3/issues/2678

Piers answered 23/3, 2016 at 15:54 Comment(2)
As I mentioned on the GitHub issue I pulled the latest build from MyGet and it didn't work.Puckery
Ok, then we need to debug more Steve.Piers
Y
0

Using current source from Git, I still see this problem. It appears to me that AuthenticationController.Logout is hit twice during the logout. Once prior to the external provider's logout page is displayed, and once after. The initial call Queues and clears the signout cookie so that the second time it is not available when rendering the logout page.

Youngster answered 23/3, 2016 at 18:33 Comment(1)
I posted this on the GitHub issue, but I'll put it here to for posterity, my AuthenticationController.Logout method is only getting his once before the redirect to the upstream provider, so this is not the cause.Puckery

© 2022 - 2024 — McMap. All rights reserved.