3

I have a DunendeSoftware Identity Server (v5.0.0-preview2.13) running under ASPNetCore 5.0 and using HttpSys as the web server (no Kestrel or IIS). I have a second website (call it protectedsite) using implicit flow to authenicate to the Identity Server. It is also ASPNetCore 5.0 hosted in HttpSys. I can Login and make authenticated calls on my protectedsite. I can logout by calling a the following code in the protectedsite

[AllowAnonymous]
public IActionResult Logout ()
{
  return SignOut(new AuthenticationProperties
  {
     RedirectUri = "/home/loggedout"
  }, "Cookies", "oidc");
}

The problem comes in the call back to home/loggedout. The code in OpenIdConnectHandler.HandleSignOutCallbackAsync receives control anytime a request is made to home/Loggedout and the actual action is never invoked. The intercept is done after the Signout code in IDS completes AND even if I do https://ProtectedSite/Home/LoggedOut without even touching IDS.

It appears that OpenIdConnectHandler.HandleSignOutCallbackAsync will intercept any url that looks like OpenIdConnectOptions.SignedOutCallbackPath. If I remove SignedOutCallbackPath setting in my startup, then the request is not intercepted and the action runs, BUT then IDS declares my PostLogoutRedirectUri to be invalid because the default of https://protectedsite/signout-callback-oidc is not in the list of valid post logout urls.

At this point my bottom line problem is that I can either have a redirect from IDS to my protected site that does not really work (leaves a blank page on the screen) or I can forego the redirect to protectedsite and the standard Duende logout message is displayed while still on the Duende IDS site. I do not want the user left on the Duende site.

OIDC setup in protectedsite

    AddOpenIdConnect("oidc", options =>
      {
        options.Authority = "https://IDS:443";
        options.ClientId = "mvc";
        options.ClientSecret = "secret";
        options.GetClaimsFromUserInfoEndpoint = true;
        options.ResponseType = "code";
        options.SaveTokens = true;
        options.Scope.Add("api1");
        options.Scope.Add("offline_access");
        options.Scope.Add("profile");
        options.SignInScheme = "Cookies";
        options.SignedOutCallbackPath = 
          Microsoft.AspNetCore.Http.PathString.FromUriComponent("/home/loggedout");
 //     options.SignedOutRedirectUri = "https://protectedsite/home/loggedout";
      });
SOHO Developer
  • 583
  • 4
  • 16

2 Answers2

3

This has been a journey due to bad/missing documentation and bad examples. Note that the (code supplied by @tore above) shows no return from the Logout action. That appears to be a very important part of the solution. Also just after the response to this question was made, I stumbled on a post at Bug Report about RedirectUri not working which describes the path of a logout redirect through ASPNetIdentity. It cannot be summarized here, but as long as the link is available, it should be consulted. Had I found it two days earlier it would have saved me a day and half of stepping through code.

My problems were many.
Lesson Learned do not return a result from the logout action.

Notice the change below to Startup in the protected site

AddOpenIdConnect("oidc", options =>
      {
        options.Authority = "https://IDS:443";
        options.ClientId = "mvc";
        options.ClientSecret = "secret";
        options.GetClaimsFromUserInfoEndpoint = true;
        options.ResponseType = "code";
        options.SaveTokens = true;
        options.Scope.Add("api1");
        options.Scope.Add("offline_access");
        options.Scope.Add("profile");
        options.SignInScheme = Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme;
//        options.SignedOutCallbackPath = Microsoft.AspNetCore.Http.PathString.FromUriComponent("/home/loggedout");
        options.SignedOutRedirectUri = "/home/loggedout";
      });

Lesson learned is the SignedOutCallbackPath is the uri that will be called on the protected site after the callback. It defaults to /signout-callback-oidc and, as far as I can tell, should be left as the default, just ensure that you set the PostLogoutRedirectUris of the IDS Client configuration to the same value.

Finally, I offer this note. I do not know if a RedirectUri can be supplied in the logout action and made to work as a runtime decision. I currently am of the opinion that it can be IF and ONLY IF, the URI there is also set in the PostLogoutRedirectUris of the IDS client configuration. I also believe, but do not know, that if the redirected to site honors the SignedOutRedirectUri, it would be followed on the site picked by the RedirectUri, but I have not attempted to prove either of these theories.

SOHO Developer
  • 583
  • 4
  • 16
1

Its a classic problem where you want to return something from your LogOut action method.

But the proper way to do logout is to not return anything from the method. Internally SignOutAsync will generate its own response.

So, the proper way is to do the following:

/// <summary>
/// Do the logout
/// </summary>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Logout()
{
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • Thanks for the response. Your code and my existing configuration got me to the point of a redirect to the default page on protectedsite. Some work finally got me to the redirect I wanted and given it required several code changes, I am going to post my own answer, but I do consider your answer as extremely helpful. – SOHO Developer Dec 30 '20 at 11:54