Skip to content
This repository has been archived by the owner on Dec 13, 2022. It is now read-only.

Logging out from Identity Server 4 won't log out from Client #3153

Closed
jfcaldeira opened this issue Apr 1, 2019 · 14 comments
Closed

Logging out from Identity Server 4 won't log out from Client #3153

jfcaldeira opened this issue Apr 1, 2019 · 14 comments
Labels

Comments

@jfcaldeira
Copy link

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...

@LindaLawton
Copy link
Contributor

Can you add your start up specifically the AddAuthentication section.

@jfcaldeira
Copy link
Author

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();
        }
    }`

@LindaLawton
Copy link
Contributor

LindaLawton commented Apr 8, 2019

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.

@jfcaldeira
Copy link
Author

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
                },`

@leastprivilege
Copy link
Member

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

@jfcaldeira
Copy link
Author

jfcaldeira commented Apr 8, 2019

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

@brockallen
Copy link
Member

@jculverwell
Copy link

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

@LindaLawton
Copy link
Contributor

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

@jculverwell
Copy link

Thank you Linda, really appreciate your help. I also have created #3212

@hugoqribeiro
Copy link

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?

@Quentinb
Copy link

Quentinb commented Jul 29, 2019

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

@lock
Copy link

lock bot commented Jan 10, 2020

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.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 10, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

8 participants
@jculverwell @brockallen @leastprivilege @Quentinb @LindaLawton @hugoqribeiro @jfcaldeira and others