Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot sign out the OpenIdConnect authentication of identityserver4 on ASP.NET Core 2 application

My Identity Server is using identityserver4 framework (http://localhost:9000). And I register the client on Identity Server as below.

clients.Add(
     new Client
     {
         ClientId = "customer.api",
         ClientName = "Customer services",
         AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
         RequireConsent = false,
         AllowAccessTokensViaBrowser = true,

         RedirectUris = { "http://localhost:60001/signin-oidc" },
         PostLogoutRedirectUris = { "http://localhost:60001/signout-callback-oidc" },
         ClientSecrets = new List<Secret>
         {
             new Secret("testsecret".Sha256())
         },
         AllowedScopes = new List<string>
         {
             IdentityServerConstants.StandardScopes.OpenId,
             IdentityServerConstants.StandardScopes.Profile,
             IdentityServerConstants.StandardScopes.Email,
             IdentityServerConstants.StandardScopes.OfflineAccess,
             "customerprivatelinesvn.api",                        
         },
         AllowOfflineAccess = true,
         AlwaysIncludeUserClaimsInIdToken = true,
         AllowedCorsOrigins = { "http://localhost:60001" }
     });  

Here is the authentication on my client app (http://localhost:60001).

private void AddAuthentication(IServiceCollection services)
{
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";       
    })
    .AddCookie()
    .AddOpenIdConnect("oidc", options =>
    {
        Configuration.GetSection("OpenIdConnect").Bind(options);        
    });    
}    

"OpenIdConnect": {
    "SignInScheme": "Cookies",
    "Authority": "http://localhost:9000/",
    "RequireHttpsMetadata": false,
    "ClientId": "customer.api",
    "ClientSecret": "testsecret",
    "Scope": [ "customerprivatelinesvn.api", "offline_access" ],
    "CallbackPath": "/signin-oidc",
    "ResponseType": "code id_token token",
    "GetClaimsFromUserInfoEndpoint": true,
    "SaveTokens": true
  }

HomeController of client app

[Authorize]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }    
}

Here are the Cookies of client app after user logs in. enter image description here

I try to implement the signout action as below

public class AccountController : Controller
{
    public async Task<IActionResult> Signout()
    {
        await HttpContext.SignOutAsync("Cookies");
        await HttpContext.SignOutAsync("oidc");

        return RedirectToAction("Index", "Home");                  
    }
}

But when user signs out, it doesn't call the endsession endpoint of identity server. I look at the traffic of fiddler, there is no request to identity server.

enter image description here

My expectation is when user signs out, it will call endsession endpoint of identity server and redirect to logout link of identity server as below.

enter image description here

enter image description here

We can do this easily on MVC application by calling OwinContext signout

private void LogoutOwin(IOwinContext context)
        {
            context.Authentication.SignOut();
        }

But the signout method doesn't work anymore on ASP.NET Core 2.

Note: I'm calling the signout action from an AJAX post because my client app is angular 5 app.

Does anyone know how to implement the signout correctly on ASP.NET Core 2?

Thank you very much.

Regards,

Kevin

like image 949
Kevin Hoang Avatar asked Nov 25 '17 19:11

Kevin Hoang


3 Answers

To allow the signing out to occur use the following Logout action:

public async Task Logout()
{
    await HttpContext.SignOutAsync("Cookies");
    await HttpContext.SignOutAsync("oidc");
}

This is exactly what the quickstart says to use (http://docs.identityserver.io/en/release/quickstarts/3_interactive_login.html). You (and I) have been too clever. I looked at the action in the tutorial and thought 'That's not complete, it doesn't return an action result, lets redirect back to my page'.

Actually what happens is HttpContext.SignOutAsync("oidc"); sets a default ActionResult (Which is to redirect to the OpenIdConnect provider to complete the sign-out). By specifying your own with return RedirectToAction("Index", "Home"); you override this, so the sign-out action never happens.

Redirect after logout

From this answer, the way you specify a redirect URL after the logout is completed is by using AuthenticationProperties

public async Task Logout()
{
   await context.SignOutAsync("Cookies");
   var prop = new AuthenticationProperties
   {
       RedirectUri = "/logout-complete"
   };
   // after signout this will redirect to your provided target
   await context.SignOutAsync("oidc", prop);
}
like image 182
Wilco Avatar answered Nov 09 '22 13:11

Wilco


On Net Core 2.0 change your code to use the enumerations CookieAuthenticationDefaults and OpenIdConnectDefaults

    services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie()
        .AddOpenIdConnect(SetOpenIdConnectOptions);


private static void SetOpenIdConnectOptions(OpenIdConnectOptions options)
{
    options.ClientId = "auAuthApp_implicit";
    options.Authority = "http://localhost:55379/";

    options.SignInScheme = "Cookies";
    options.RequireHttpsMetadata = false;

    options.SaveTokens = true;
    options.ResponseType = "id_token token";
    options.GetClaimsFromUserInfoEndpoint = true;

}

and...

public async Task<IActionResult> Logout()
{
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);

    return RedirectToAction("Index", "Home");
}
like image 25
alhpe Avatar answered Nov 09 '22 15:11

alhpe


I can resolve my problem now.

1) Return SignOutResult will call endsession endpoint.

2) Change AJAX post to submit form.

public class AccountController : Controller
{
    public IActionResult Signout()
    {
        return new SignOutResult(new[] { "oidc", "Cookies" });            
    }
}


<form action="/Account/Signout" id="signoutForm" method="post" novalidate="novalidate">
    <ul class="nav navbar-nav navbar-right">
        <li><a href="javascript:document.getElementById('signoutForm').submit()">Sign out</a></li>
    </ul>
</form>
like image 17
Kevin Hoang Avatar answered Nov 09 '22 15:11

Kevin Hoang