Tanner Taylor
Tanner Taylor

Reputation: 81

ASP.NET Core Auth not staying signed in after browser closes (Azure AD B2C)

I've successful set up authentication with Azure AD B2C in my ASP.NET Core Blazor application. I can open the website (https://localhost:5001) in multiple tabs without signing in again. However, if I keep the server running but close and reopen the browser and navigate to the website, it requires me to sign in again. My understanding was that it should have kept me signed in between browser sessions. I'm fairly new to all this, so I'm not even sure where to begin looking for the problem. Any ideas what this might be?

Here is my Startup.cs, if it helps.

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

    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"));
            
        services.AddControllersWithViews().AddMicrosoftIdentityUI();

        services.AddAuthorization(options =>
        {
            // By default, all incoming requests will be authorized according to the default policy
            options.FallbackPolicy = options.DefaultPolicy;
        });
            
        services.AddRazorPages();
        services.AddServerSideBlazor().AddMicrosoftIdentityConsentHandler();
    }

    // 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();
        }
        else
        {
            app.UseExceptionHandler("/_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.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

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

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        });
    }

Upvotes: 4

Views: 1793

Answers (2)

Mohammad Komaei
Mohammad Komaei

Reputation: 9694

I had this problem and resolved it by IsPersistent = true in SignInAsync() method of login page. I use .net 8 blazor ssr Cookie Authentication.

public async Task Login(EditContext editContext)
{
    var user = await appDbContext.Users.AsNoTracking()
        .FirstOrDefaultAsync(u => u.Email == vm.Email.ToLower());
    if (user is null || passwordService.VerifyHashedPasswordV3(user.PasswordHash, vm.Password) == false)
    {
        ShowAlert = true;
        AlertText = "The email or password is incorrect.";
        return;
    }

    var claims = new List<Claim>
    {
        new Claim(Constants.UserIdClaim, user.Id.ToString()),
        new Claim(Constants.EmailClaim, vm.Email),
        //new Claim(Constants.RoleClaim, user.RoleId.ToString())
    };
    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    await HttpContextAccessor.HttpContext!.SignInAsync(claimsPrincipal, new AuthenticationProperties { IsPersistent = true });
    NavigationManager.NavigateTo(ReturnUrl == null ? "/Panel" : ReturnUrl);
}

Also need builder.Services.AddAuthentication() and app.UseAuthorization() in Program.cs :

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
 .AddCookie(options => { options.LoginPath = "/Login"; options.ExpireTimeSpan = TimeSpan.FromDays(30); });

Upvotes: 0

Tanner Taylor
Tanner Taylor

Reputation: 81

I found the solution to my problem. Thanks to @huysentruitw for putting me on the right path.

TL;DR: Creating my own AccountController and setting AuthenticationProperties.IsPersistent to true on sign in fixed the expiration of the auth cookie and ultimately solved my problem.

The problem was basically that the auth token cookie was not being persisted across browser sessions. It would always create the cookie, but looking in DevTools, the expiration was always set to "Session". However, configuring the expire times via the options available in Startup.cs didn't resolve it either. I tried the following:

  • Setting it via ConfigureApplicationCookie with CookieAuthenticationOptions.Cookie.Expires.
  • Setting it via ConfigureApplicationCookie with CookieAuthenticationOptions.ExpireTimeSpan
  • Setting it via .AddMicrosoftIdentityWebApp using the overload to provide configureCookieAuthenticationOptions with a CookieBuilder.Expires property populated.
  • A few other obscure things that I didn't expect to work anyway, but I was looking for anything to work.

No matter what I did, the cookie came through as expiring after the "Session".

So, I stepped into the ASP.Net Core authentication code (the CookieAuthenticationHandler class in Microsoft.AspNetCore.Authentication.Cookies) to find out why the expiration value wasn't being used. I found that CookieOptions.Expires is overridden for some reason (in the BuildCookieOptions method) and that the expiry is only set if AuthenticationProperties.IsPersistent is true (you can see this happening in HandleSignInAsync method). So, my next step was to figure out how to set that to true. I found that the AuthenticationProperties are set by an AccountController that is added automatically by the call in Startup.cs to .AddMicrosoftIdentityUI. I copied this AccountController to my project as a starting point instead and got rid of the call to .AddMicrosoftIdentityUI. I then updated the AuthenticationProperties to set IsPersistent to true, which is ultimated what fixed the issue. The cookie now comes through with an expiration date/time.

I don't know if I missed something, but it certainly seems like a bug to me, or at least a really poor configuration experience. Hopefully someone will come along and potentially point out my blunder, but this is what worked for me. I think it makes more sense to pull the AccountController into my project anyway so that I can see what's going on and fine tune it as needed.

Upvotes: 4

Related Questions