1

I created AspNetCore 3.1 Project and added IdentityServer4 for SSO (added 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' package). Sig-nin works fine, but logout doesn't.

In Startup.cs I have the following configuration for IdentityServer :

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
                { ...
                    var client = new Client
                    {
                        ClientName = "ssotestclient",
                        ClientId = "ssotestclient",
                        ClientSecrets = { new Secret("somesecret".Sha256()) },
                        AllowedGrantTypes = GrantTypes.Code.Union(GrantTypes.ResourceOwnerPasswordAndClientCredentials).ToArray(),
                        RequirePkce = false,
                        RequireClientSecret = false,
                        AllowOfflineAccess = false,
                        AlwaysSendClientClaims = true,
                        UpdateAccessTokenClaimsOnRefresh = true,
                        AlwaysIncludeUserClaimsInIdToken = true,
                        AllowedScopes = new List<string>
                        {
                            IdentityServerConstants.StandardScopes.OpenId,
                            IdentityServerConstants.StandardScopes.Email,
                            IdentityServerConstants.StandardScopes.Profile,
                            IdentityServerConstants.StandardScopes.OfflineAccess
                        },
                        RequireConsent = false,
                        RedirectUris = {"https://mytestsite.local/signin-oidc" },
                        PostLogoutRedirectUris = {"https://mytestsite.local/signout-callback-oidc"}
                    }
                    options.Clients.Add(client);
                 });

The client Website is an AspNetCore MVC project with 'Microsoft.AspNetCore.Authentication.OpenIdConnect' package. Client initialization in Startup.cs

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(options =>
                {
                    options.DefaultScheme = "Cookies";
                    options.DefaultChallengeScheme = "oidc";
                })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.Authority = "https://myssoserver.local";
                options.RequireHttpsMetadata = false;
                HttpClientHandler handler = new HttpClientHandler();
                handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
                options.BackchannelHttpHandler = handler;
                options.ClientId = "ssotestclient";
                options.ClientSecret = "secret";
                options.ResponseType = "code";
                options.UsePkce = true;
                options.Scope.Add("email");
                options.Scope.Add("profile");
                options.SaveTokens = true;
            });
            services.AddAuthorization();
            services.AddRazorPages();
            services.AddControllers();
        }

Logout button on the client website has the following code :

        public async Task OnPostAsync(string returnUrl = null)
        {
            await HttpContext.SignOutAsync("Cookies");
            await HttpContext.SignOutAsync("oidc");
        }

After this the browser is redirected to SSO server with the following location:

https://myssoserver.local/connect/endsession?post_logout_redirect_uri=https%3A%2F%2Fmytestsite.local%2Fsignout-callback-oidc&id_token_hint=<token>&state=<state>&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0

Actual values of token and state are trimmed from this post because they are too long.

As you can see the post_logout_redirect_uri, id_token_hint and state parameters are passed to the server endsession endpoint. And on the SSO server side I see the message that it has passed validation:

info: IdentityServer4.Validation.EndSessionRequestValidator[0]
      End session request validation success
      {
        "ClientId": "sso_test_client",
        "ClientName": "sso_test_client",
        "SubjectId": "f3693d8c-6095-4f1a-9f8f-bdc7440e9395",
        "PostLogOutUri": "https://mytestsite.local/signout-callback-oidc",
        "State": "<state>",
        "Raw": {
          "post_logout_redirect_uri": "https://mytestsite.local/signout-callback-oidc",
          "id_token_hint": "***REDACTED***",
          "state": "<state>",
          "x-client-SKU": "ID_NETSTANDARD2_0",
          "x-client-ver": "5.5.0.0"
        }
      }

However after this request the browser receives 302 redirect to https://myssoserver.local/Identity/Account/Logout page instead of provided post_logout_redirect_uri. And actual logout doesn't happen, because only OnGet() handler is called that does nothing

Could not find similar issue on the web.

What could be wrong? Why do I get redirected to the Logout page instead of a callback uri? I searched through the project code and could not find any reference with the 'Logout' word apart from Logout page itself

mistika
  • 2,363
  • 2
  • 21
  • 25

1 Answers1

0

One thought is that you need to set the LogoutPath.

.AddCookie(options => { options.LogoutPath = "/User/Logout"; })

LogoutPath is a security feature, just as this picture from one of my training classes shows:

enter image description here

Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • The problem is that browser doesn't call POST on the logout page on SSO server. It's redirected with GET – mistika Mar 18 '21 at 16:22
  • You can ignore the HttpPost attribute in my picture, however, its best practice to have the logout button do a post and not a GET. see https://security.stackexchange.com/questions/62769/should-login-and-logout-action-have-csrf-protection – Tore Nestenius Mar 18 '21 at 19:08
  • I don't want to have user click logout button on the Application server and then on the SSO server. I want a logout button on Application server that will log out user from SSO as well. – mistika Mar 22 '21 at 17:19
  • doesn't calling both here await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc"); sign you out from both? in general it should. – Tore Nestenius Mar 23 '21 at 08:37
  • It should, but it doesn't. Is logs out only from Client Server, but not from the SSO server. So next time I refresh the client server page, I automatically get a new token from SSO because I'm still logged in. – mistika Mar 23 '21 at 09:33
  • can you add the code to your logout button in the question? – Tore Nestenius Mar 23 '21 at 09:49
  • Well, it was there, just added method signature for Logout button handler (OnPostAsync) – mistika Mar 23 '21 at 13:28
  • What does the client code (AddOpenIdConnect, AddCookie) in the client look like? – Tore Nestenius Mar 23 '21 at 13:48
  • Added the client's ConfigureServices() – mistika Mar 23 '21 at 19:55
  • I recomend developers using IdentityServer to put it and the client and API in separate isntances, otherwise it is really hard to reson about it, as I explain here https://stackoverflow.com/questions/66709813/grpc-web-rpcexception-bad-grpc-response-invalid-content-type-value-text-html/66710680?noredirect=1#comment118038507_66710680 – Tore Nestenius Mar 24 '21 at 09:24