title: Sign-out IdentityServer4 + ASP.NET Core MVC + Angular application
body: <p>I have a problem with proper return to client application after successful logout. First I would like to describe my setup.</p>
  
  <ul>
  <li>IdentityServer4 as IDP</li>
  <li>2x ASP.NET Core MVC application (client from IS4 perspective). The MVC applications are using two authentication schemes ("oidc"-cookie based scheme for auth and JWT Bearer token scheme for rest api calls)</li>
  <li>2x Angular 7 SPA (that is hosted by above MVC app)</li>
  </ul>
  
  <p>Because my project has high security requirements I'm using the <strong>Hybrid flow</strong> for authorization. <strong>So the login flow is as follows:</strong></p>
  
  <ol>
  <li>User is not authenticated and visits SPA application, where he clicks on signin button</li>
  <li>Angular application makes a redirect to the MVC endpoint that is under [Authorize] attribute, which initiates the OIDC challenge.</li>
  <li>User is being redirected (by OidcMiddleware) to IS4 where he can type in username and password</li>
  <li>User is being redirected (after successful login) back to MVC login endpoint logic (that was called in pt. 2), which is responsible for redirecting the user back to Angular SPA.</li>
  <li>Angular makes a http call to a MVC endpoint that is under [Authorize] and  is responsible for returning the generate by IS4 JWT token to the client.</li>
  <li>Because user is logged in into the MVC client (cookies present) the MVC backend extracts the JWT token (access_token) from the cookie and return it to the client</li>
  <li>Login flow has finished at this moment (angular uses for REST API calls the JWT token) - because of that I'm using on the backend the two authentication schemes.</li>
  </ol>
  
  <p><strong>So now I'm facing some issues with logout functionality. First would like to describe the flow for signout:</strong></p>
  
  <ol>
  <li>Signed in user clicks on Signout button in angular application</li>
  <li><p>The Spa application clears localstorage entries (where the JWT token is stored) and redirects the user to the logout action on the MVC application. Code bellow:</p>
  
  <p>[AllowAnonymous]
      public async Task Logout(string returnUrl)
      {
          await HttpContext.SignOutAsync("Cookies");
          await HttpContext.SignOutAsync("oidc");
      }</p></li>
  <li><p>The SignoutAsync on "oidc" scheme triggers the OIDC signout flow (which redirects to IS4 signout endpoint)</p></li>
  <li><p>The IS4 Logout endpoint is loading the logout context (code from IS4 Quickstart)</p>
  
  <pre><code>[HttpPost]
  [ValidateAntiForgeryToken]
  public async Task&lt;IActionResult&gt; Logout(LogoutDto dto)
  {
      // build a model so the logged out page knows what to display
      var vm = await BuildLoggedOutViewModelAsync(dto.LogoutId);
  
      if (User?.Identity.IsAuthenticated == true)
      {
          // delete local authentication cookie
          await HttpContext.SignOutAsync();
  
          // raise the logout event
          await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
      }
  
      // check if we need to trigger sign-out at an upstream identity provider
      if (vm.TriggerExternalSignout)
      {
          // build a return URL so the upstream provider will redirect back
          // to us after the user has logged out. this allows us to then
          // complete our single sign-out processing.
          string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
  
          // this triggers a redirect to the external provider for sign-out
          return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
      }
  
      return View("LoggedOut", vm);
  }
  </code></pre></li>
  <li><p>Important is the BuildLoggedOutViewModelAsync method, which loads the logout context:</p>
  
  <p>private async Task BuildLoggedOutViewModelAsync(string logoutId)
      {
          // get context information (client name, post logout redirect URI and iframe for federated signout)
          var context = await _interaction.GetLogoutContextAsync(logoutId);</p>
  
  <pre><code>    var vm = new LoggedOutViewModel
      {
          AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
          PostLogoutRedirectUri = context?.PostLogoutRedirectUri,
          ClientName = string.IsNullOrEmpty(context?.ClientName) ? context?.ClientId : context?.ClientName,
          SignOutIframeUrl = context?.SignOutIFrameUrl,
          LogoutId = logoutId
      };
  
      if (User?.Identity.IsAuthenticated == true)
      {
          var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
          if (idp != null &amp;&amp; idp != IdentityServerConstants.LocalIdentityProvider)
          {
              var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
              if (providerSupportsSignout)
              {
                  if (vm.LogoutId == null)
                  {
                      // if there's no current logout context, we need to create one
                      // this captures necessary info from the current logged in user
                      // before we signout and redirect away to the external IdP for signout
                      vm.LogoutId = await _interaction.CreateLogoutContextAsync();
                  }
  
                  vm.ExternalAuthenticationScheme = idp;
              }
          }
      }
  
      return vm;
  }
  </code></pre></li>
  <li><p>And here is the issue. In the case, that my mvc cookie has expired, the context has most properties set to null like for example the PostLogoutRedirectUrl. And because of that I'm not able to display the return to client link on the IS4 LoggedOut.cshtml view.</p></li>
  </ol>
  
  <p><strong>In the case, that my mvc client cookie is not expired (valid) everything is working fine. I have the PostLogoutRedirectUrl, which allows me to return from IS4 back to the client which triggered the signout.</strong></p>
  
  <p>Guys do you have some ideas, how could I fix that issue? A big thank you in advance!</p>