Azure B2C Sign Out, then Sign In does not challenge the user with a password prompt

Viewed 150

I am using Azure AAD B2C to manage my users and auth flow. I am trying to create a user experience where the user can log in under a different account. The user should be able to Log Out of their account and then click Sign In and be able to provide a different username and password.

However, currently I sign out. I can confirm through F12 debugger that all cookies are cleared. Then I click sign in and it signs me back into my previous account without challenging me for a username and password.

I'm not sure what is happening or why. Here is my Sign In code.

public IActionResult SignIn([FromRoute] string scheme)
    {
        scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
        var redirectUrl = Url.Content("~/");
        var properties = new AuthenticationProperties { RedirectUri = redirectUrl, AllowRefresh = true };
        properties.Items["policy"] = "B2C_1_SignUpIn";
        return Challenge(properties, scheme);
    }

Here is my Sign Out code.

public async Task<IActionResult> SignOutAsync([FromRoute] string scheme)
    {
        HttpContext.Session.Clear();
        if (HttpContext.Request.Cookies.Count > 0)
        {
            var test = HttpContext.Request.Cookies.ToList();
            var siteCookies = HttpContext.Request.Cookies.Where(c => c.Key.Contains(".AspNetCore.") || c.Key.Contains("Microsoft.Authentication"));
            foreach (var cookie in siteCookies)
            {
                Response.Cookies.Delete(cookie.Key);
            }
        }

        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);

        return Redirect("~/");
    }

My Startup.cs looks like this.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDistributedMemoryCache();
        services.AddMicrosoftIdentityWebAppAuthentication(
            Configuration, 
            "AzureAdB2C", 
            OpenIdConnectDefaults.AuthenticationScheme, 
            CookieAuthenticationDefaults.AuthenticationScheme);

        services.AddMvc();
        services.AddSession();  
        services.AddRazorPages();
        services.AddControllersWithViews();

        services.Configure<OpenIdConnectOptions>(Configuration.GetSection("AzureAdB2C"));
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseMigrationsEndPoint();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        

        app.UseSession();
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication().UseCookiePolicy();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
                //endpoints.MapAreaControllerRoute(  
                //                  name: "Identity",  
                //                  areaName: "Identity",  
                //                  pattern: "Identity/{controller=Home}/{action=Index}");  
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}");
            endpoints.MapRazorPages();
        });
    }
}

My UI references the User.Identity.IsAuthenicated to determine if user information is present. Any help why it is behaving this way by automatically signing me in after I have signed out would be very helpful. I am new to OIDC and Azure B2C but I feel like I'm missing something very basic. Thanks in advance.

3 Answers

Diving deep into Microsoft docs for over a month I finally found this document.

Microsoft Docs - OIDC send a sign-out request

I had actually read through this doc a few times and then today I finally noticed a little asterisk.

client-id requirement

Up until this point I had only been sending required parameters in the query string of my get request. Even though client_id is marked as not required, it is in my case because I am using Application isolation SSO and require ID token on logout request is set to "No".

I changed my redirect uri by adding the client_id query parameter to the logout uri. My final SignOut Action looks like the following.

public IActionResult SignOut([FromRoute] string scheme)
    {
        HttpContext.Session.Clear();
        if (HttpContext.Request.Cookies.Count > 0)
        {
            var test = HttpContext.Request.Cookies.ToList();
            var siteCookies = HttpContext.Request.Cookies.Where(c => c.Key.Contains(".AspNetCore.") || c.Key.Contains("Microsoft.Authentication"));
            foreach (var cookie in siteCookies)
            {
                Response.Cookies.Delete(cookie.Key);
            }
        }
        var redirectUrl = Url.Content($"https://mytenantname.b2clogin.com/mytenantname.onmicrosoft.com/myuser_flow_policy_name/oauth2/v2.0/logout?post_logout_redirect_uri=https://localhost:44370/&client_id=my_app_registraion_client_guid");
        var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
        
        return SignOut(properties, scheme);
    }

Are you also clearing the logged in status on the Azure side? As Azure B2C is your Identity provider you will need to log the user out from both your application and B2C to force them to have to re enter their credentials.

This can be done by redirecting your user to the sign out URL from the OpenId config to clear the auth cookies from the B2C side as well after you have cleared the auth cookies for your application.

You can even include a redirect URL as a query parameter to redirect the user back to a specific page on your website.

Add the below method to AccountController and the signout should clear the local as well as remote login. This should also handle the signout redirect. Hope this helps.

[HttpGet("{scheme?}")]
    public IActionResult SignOut(
        [FromRoute] string scheme, 
        [FromQuery] string redirectUri)
    {
        scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
        string redirect;
        if (!string.IsNullOrEmpty(redirectUri) && Url.IsLocalUrl(redirectUri))
        {
            redirect = redirectUri;
        }
        else
        {
            redirect = Url.Content("~/")!;
        }
        return SignOut(
             new AuthenticationProperties
             {
                 RedirectUri = redirect,
             },
             CookieAuthenticationDefaults.AuthenticationScheme,
             scheme);
    }
Related