In Blazor Server 6.0, cookie authentication scheme is used and cookie is being set. However, after cookie expires, navigation to other links in the app - the requests are still being served to the components.
Also, if I open multiple tabs of each component, logout from 1st tab deletes the cookie, but 2nd and 3rd tabs are still active and components are loaded with content.
- Version: .NET 6.0
- Template: Blazor Server
- Authentication scheme: cookies
- Expiration time: 2 minutes
- Revalidation interval: 1 minute
This is the code used:
builder.Services.AddAuthentication(options =>{ options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;}).AddCookie(options =>{ options.Cookie.IsEssential = true; options.Cookie.SameSite = SameSiteMode.Strict; options.LoginPath = new PathString("/login"); options.Cookie.Name = AppSettings.CookieName; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = builder.Environment.IsDevelopment() ? CookieSecurePolicy.None : CookieSecurePolicy.Always; options.ExpireTimeSpan = TimeSpan.FromMinutes(2); options.SlidingExpiration = false; options.AccessDeniedPath = new PathString("/account/denied");});During user authentication, setting SessionId to ClaimType.Sid and checking if this value exists for revalidating user.
- How to handle this situation to logout the user in subsequent tabs
- Cookie expires, but requests are still being served
Custom state provider:
public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider{ private readonly IServiceScopeFactory _scopeFactory; private readonly IHttpContextAccessor _httpContextAccessor; private readonly TimeSpanSettings _timeSpanSettings; public CustomAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor, IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory, IOptions<TimeSpanSettings> timeSpanSettingsOptions) : base(loggerFactory) { _scopeFactory = scopeFactory; _httpContextAccessor = httpContextAccessor; _timeSpanSettings = timeSpanSettingsOptions.Value; } protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(1); public override Task<AuthenticationState> GetAuthenticationStateAsync() { var httpContext = _httpContextAccessor.HttpContext; if (httpContext != null && httpContext.User.Identity.IsAuthenticated) { var identity = new ClaimsIdentity(httpContext.User.Claims, "cookieauth"); var user = new ClaimsPrincipal(identity); return Task.FromResult(new AuthenticationState(user)); } else { return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))); } throw new NotImplementedException(); } public void NotifyAuthenticationStateChanged() { NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } protected override async Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState, CancellationToken cancellationToken) { var sessionId = authenticationState.User.Claims.Where(c => c.Type.Equals(ClaimTypes.Sid)).Select(c => c.Value).FirstOrDefault(); var user = authenticationState.User; if (sessionId != null && user.Identity.IsAuthenticated && user != null) { return true; } return false; }}