Identityserver4: Logging out from Identity Server 4 won't log out from Client

Created on 1 Apr 2019  路  13Comments  路  Source: IdentityServer/IdentityServer4

I'm using the Asp Net Identity and the EF Core combined sample, everything works correctly, database, seeding, api call except for when i try to log out from the IS page. It does not delete the .AspNetCore.Cookies which is the one keeping the user logged in on client

` ///


/// Handle logout page postback
///

[HttpPost]
[ValidateAntiForgeryToken]
public async Task Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

        if (User?.Identity.IsAuthenticated == true)
        {
            // delete local authentication cookie
            await _signInManager.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);
    }

On MVC Client it works correctly, logs out from both sides
public IActionResult Logout() { return SignOut("Cookies", "oidc"); }

I tried to do

// delete local authentication cookie await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc");

but gives me an exception cause i didn't add these on AddAuthentication on the IS...

question

Most helpful comment

Clearing the cookie in IdentityServer won't logout the user "auto-magically" at other clients, check the docs

https://identityserver4.readthedocs.io/en/latest/topics/signout.html

All 13 comments

Can you add your start up specifically the AddAuthentication section.

Hi , thanks for replying. This is basically the same file as the sample of Asp.Net Identity and EF Core combined

```
public class Startup
{
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }

    public Startup(IConfiguration configuration, IHostingEnvironment environment)
    {
        Configuration = configuration;
        Environment = environment;
    }

    public void ConfigureServices(IServiceCollection services)
    {

        var connectionString = Configuration.GetConnectionString("DefaultConnection");
        var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(connectionString));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);

        services.Configure<IISOptions>(iis =>
        {
            iis.AuthenticationDisplayName = "Windows";
            iis.AutomaticAuthentication = false;
        });

        var builder = services.AddIdentityServer(options =>
        {
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;
        })
            // this adds the config data from DB (clients, resources)
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = b =>
                    b.UseSqlServer(connectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));
            })
            // this adds the operational data from DB (codes, tokens, consents)
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = b =>
                    b.UseSqlServer(connectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));

                    // this enables automatic token cleanup. this is optional.
                    options.EnableTokenCleanup = true;
            })
            .AddAspNetIdentity<ApplicationUser>();

        if (Environment.IsDevelopment())
        {
            builder.AddDeveloperSigningCredential();
        }
        else
        {
            throw new Exception("need to configure key material");
        }

        services.AddAuthentication()
            .AddGoogle(options =>
            {
                    // register your IdentityServer with Google at https://console.developers.google.com
                    // enable the Google+ API
                    // set the redirect URI to http://localhost:5000/signin-google
                    options.ClientId = "copy client ID from Google here";
                options.ClientSecret = "copy client secret from Google here";
            });
    }


    public void Configure(IApplicationBuilder app)
    {
        if (Environment.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();
        app.UseIdentityServer();
        app.UseMvcWithDefaultRoute();
    }
}`

try

 await _signInManager.SignOutAsync();

if that doesn't work you can go with

   // delete local authentication cookie
  await HttpContext.SignOutAsync();
  // Clear the existing external cookie to ensure a clean login process
  wait HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
  // Clear the existing external cookie to ensure a clean login process
  await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

Remember this is NOT going to log a user out of their Google account. Google doesn't support third party logout. Its just going to log them out of your application.

I tried this and it does not delete the AspNetCore.Cookies which keeps the user logged in the client application.

I tried again just downloading the samples https://github.com/IdentityServer/IdentityServer4.Samples
and using a fresh Identity + EF Core combined solution and problem still persists.

Logging out from Identity Server does not log out from client.

This is the configuration on client, not sure if it could change anything

`` new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Hybrid,

                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },

                RedirectUris           = { "http://localhost:5002/signin-oidc" },
                PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "api1"
                },

                AllowOfflineAccess = true
            },`

Clearing the cookie in IdentityServer won't logout the user "auto-magically" at other clients, check the docs

https://identityserver4.readthedocs.io/en/latest/topics/signout.html

That makes sense, I haven't implemented any of this. However i can't seem to find any of this in any identity server 4 sample on the official github, would be nice to see a working example

Looks like the issue is that in the quickstarts layout lhttps://github.com/IdentityServer/IdentityServer4.Samples/blob/master/Quickstarts/Combined_AspId_and_EFStorage/src/IdentityServer/Views/Shared/_Layout.cshtml

the link to the logout controller does not pass the logoutId argument

 @if (!string.IsNullOrWhiteSpace(name))
            {
                <ul class="nav navbar-nav">
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">@name <b class="caret"></b></a>
                        <ul class="dropdown-menu">
                            <li><a asp-action="Logout" asp-controller="Account">Logout</a></li>
                        </ul>
                    </li>
                </ul>
            }

...which the AccountController expects

https://github.com/IdentityServer/IdentityServer4.Samples/blob/master/Quickstarts/Combined_AspId_and_EFStorage/src/IdentityServer/Quickstart/Account/AccountController.cs

```[HttpGet]
public async Task Logout(string logoutId)
{
// build a model so the logout page knows what to display
var vm = await BuildLogoutViewModelAsync(logoutId);

        if (vm.ShowLogoutPrompt == false)
        {
            // if the request for logout was properly authenticated from IdentityServer, then
            // we don't need to show the prompt and can just log the user out directly.
            return await Logout(vm);
        }

        return View(vm);
    }

As a result vm.TriggerExternalSignout is false which means the extenal signout does not get called.

/// Handle logout page postback
///
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

        if (User?.Identity.IsAuthenticated == true)
        {
            // delete local authentication cookie
            await _signInManager.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);
    }

```

I'll raise a new issue

I am in the process of going though the samples now i will add this to #3178

Thank you Linda, really appreciate your help. I also have created https://github.com/IdentityServer/IdentityServer4/issues/3212

Looks like the issue is that in the quickstarts layout lhttps://github.com/IdentityServer/IdentityServer4.Samples/blob/master/Quickstarts/Combined_AspId_and_EFStorage/src/IdentityServer/Views/Shared/_Layout.cshtml

the link to the logout controller does not pass the logoutId argument

 @if (!string.IsNullOrWhiteSpace(name))
            {
                <ul class="nav navbar-nav">
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">@name <b class="caret"></b></a>
                        <ul class="dropdown-menu">
                            <li><a asp-action="Logout" asp-controller="Account">Logout</a></li>
                        </ul>
                    </li>
                </ul>
            }

...which the AccountController expects

https://github.com/IdentityServer/IdentityServer4.Samples/blob/master/Quickstarts/Combined_AspId_and_EFStorage/src/IdentityServer/Quickstart/Account/AccountController.cs

        public async Task<IActionResult> Logout(string logoutId)
        {
            // build a model so the logout page knows what to display
            var vm = await BuildLogoutViewModelAsync(logoutId);

            if (vm.ShowLogoutPrompt == false)
            {
                // if the request for logout was properly authenticated from IdentityServer, then
                // we don't need to show the prompt and can just log the user out directly.
                return await Logout(vm);
            }

            return View(vm);
        }

As a result vm.TriggerExternalSignout is false which means the extenal signout does not get called.

/// Handle logout page postback
        /// </summary>
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Logout(LogoutInputModel model)
        {
            // build a model so the logged out page knows what to display
            var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

            if (User?.Identity.IsAuthenticated == true)
            {
                // delete local authentication cookie
                await _signInManager.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);
        }

I'll raise a new issue

You've got a point here! I think.

The problem is that BuildLogoutViewModelAsync, when logoutid is null (as is in the case of the IS account controller), will not set LoggedOutViewModel.SignOutIframeUrl.

This means that if the user signs-out in the IS backoffice it will not trigger the front-channel logout on the clients as it does between clients.

Anyone can tell me how to fix that please?

I have the same issue, except I'm using a JavaScript Client with OIDC. I can see that the endSession contains both the id_token_hint and post_logout_redirect_uri in the debug logs, I can see an error:

Client request:
http://localhost:5002/connect/endsession?id_token_hint=ey_redacted_EQ&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A8080

Server logs:
{"ClientId": "js", "ClientName": "JavaScript Client", "SubjectId": "0exxx93", "PostLogOutUri": null, "State": null, "Raw": {"id_token_hint": "eyxxx

[INF] End session request validation failure: Invalid post logout URI [ERR] Error processing end session request Invalid request

I assume I've done something wrong, but have not scratched open the solution yet.
PS: related reference https://identityserver4.readthedocs.io/en/latest/endpoints/endsession.html

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings