How to invalidate the session after log out in IdentityServer4

Viewed 918

We have website with SSO using IdentitySrver4. We recently tested our site for security and we found one vulnerability which is as follow,

A session token for the application remained valid (and could be used to authenticate requests to the application) even after the logout function had been invoked in the associated session. This indicated that the session termination mechanism was not fully effective and increased the possibility of unauthorised access to the application. It should be noted that the tokens did have an effective time out after a period of time. The logout function terminated the associated session client-side (by removing the session cookie from the user’s browser) but the session remained valid server-side. Requests which were made after the logout function had been used, but which provided the original session cookie, continued to be successful.

Following are code snippets,

IdentityServer

StartUp ConfigureServices:-

services.AddIdentityServer(.......
services.AddAuthentication(options =>
               {
                   options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                   options.DefaultChallengeScheme = "oidc";
               })
               .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opt=> {

                   opt.ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToInt32(Configuration["CookieTimeOut"]));               
                   //This has time limit of 30 minutes    
               })
               .AddOpenIdConnect("oidc", opts =>
              {.......

The Login code is as follow,

await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, cp);

LogOut method in IdentityServer:-

[HttpGet]
        public async Task<IActionResult> Logout(string clientId, string returnUrl, string culture)
        {
            var clientsList = new List<Client>();

            // delete authentication cookie
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            var cookiesToBeDeleted = Request.Cookies.Keys;
            foreach (string cookie in cookiesToBeDeleted)
            {
                Response.Cookies.Delete(cookie);
            }

            var logoutURL = _configuration["DefaultLogoutRedirectUrl"];

            if (Uri.IsWellFormedUriString(returnUrl, UriKind.Absolute))
            {
                //TODO: validate that the return url belongs to the client who has initiated the logout request
                //if the url validation fails then we should return to a pre-determined url that is mentioned in the config                
                logoutURL = returnUrl;
            }

            var vm = new LoggedOutViewModel()
            {
                PostLogoutRedirectUri = logoutURL,
                SignOutUrls = _clients.Value
                              .Where(client => !string.IsNullOrWhiteSpace(client.FrontChannelLogoutUri))
                              .Select(client => client.FrontChannelLogoutUri),
                ClientName = clientId,
                AutomaticRedirectAfterSignOut = true
            };

            //If there is no return url then display a local logged out page
            return View("LoggedOut", vm);
        }

        [HttpGet]
        public IActionResult LoggedOut(string returnUrl)
        {
            var vm = new LoggedOutViewModel()
            {
                PostLogoutRedirectUri = returnUrl,
                SignOutUrls = null,
                AutomaticRedirectAfterSignOut = true
            };
            return View(vm);
        }

We have used FrontChannel logouts,Here all the client's "FrontChannelLogoutUri" are rendered in "IFrame" in logout page of the Identity Server.

Client Code (MVC App):-

StartUp ConfigureServices:-

services.AddAuthentication(options =>
                {
                    options.DefaultScheme =
                        CookieAuthenticationDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = "oidc";
                })
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, ck =>
                {
                    ck.Cookie.Name = "ClientCookie";    
                    ck.ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToInt32(Configuration["CookieTimeOut"]));
                    //This also has value of 30 minutes. 
                })
                .AddOpenIdConnect("oidc", opts =>
                {.......

LogOut function in Client App :-

public async Task<IActionResult> Logout(string path)
        {                
            var logoutUrl = //This is IdentityServer LogOut Method URL
            
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            var prop = new Microsoft.AspNetCore.Authentication.AuthenticationProperties()
            {
                RedirectUri = logoutUrl
            };
            await HttpContext.SignOutAsync("oidc", prop);
            return Redirect(logoutUrl);
        }

The FrontChannel method for Client App:-

[AllowAnonymous]
        public async Task<IActionResult> ForcedSignout()
        {            
            var cookiesToBeDeleted = Request.Cookies.Keys;
            foreach (string cookie in cookiesToBeDeleted)
            {
                Response.Cookies.Delete(cookie);
            }
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            
            return View();
        }

The logout workflow is as follow:-

When logout is called from Client MVC App, we have called both HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme) & HttpContext.SignOutAsync("oidc", prop).

Then user is redirected to the IdentityServer Logout method, which again calls the HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme).

When Identity's logout page is rendered we generate the "IFrames" with Client's "FrontChannelLogoutUri" (in this case "ForcedSignout()" of Client App).

"ForcedSignout" method again deletes the cookies and call HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme).

Steps to replicate the issue:-

We captured the Client App's edit method in postman.

The postman request with changed data was run and it worked.

After that user was logged out of the Client App and the postman request was again run which ran successfully even though we have logged out.

Then we waited for 30 minutes (Inactivity) and tried the postman request but this time it did not work as the sessions had timed out.

We need to know, how can we remove/invalidate the session from server when the user logs out of the application? Thank you.

0 Answers
Related