I use this repo to implement authentication and authorization with cookie on the Blazor Server according to ^.
I know that I couldn't use HttpContextAccessor because of Microsoft recommendations, However, I use the HttpContext
in the .cshtml
file for login and logout same as this link, not in the Blazor component.
The problem in production phase on windows server IIS server (2022) is that if the first user logs in to the application with his username, the rest of the users are also authenticated in their computer browser with the same first username. Now, if the second user enters with his username, then all users will be authenticated with the second user's name and so on. Now, if a user presses the logout key, he will be logged out and his cookie will be deleted from the browser, but after refreshing the browser page once, he will log in again with the last verified logged-in user without going through the login process. Even then, there are no cookies in his browser. For better clarification, I have put a gif of how it works(Of course, in order to record the screen in the laboratory environment, I have simulated it on the IIS Express).
I can almost guess that the problem is the use of the HttpContext
. But all the implementations that I saw with cookies on the web for Blazor Server, none of them mentioned this problem. So I guessed that I might have made a mistake in the implementation somewhere.
Is there a way to solve my problem with the solution provided?
I have already seen the answers to these questions ^, ^, ^, However, None is the answer to my question.
I use .Net6. It's my authentication configuration on the startup.cs:
services.AddScoped<IUnitOfWork, ApplicationDbContext>();
services.AddScoped<IUsersService, UsersService>();
services.AddScoped<IRolesService, RolesService>(); services.AddScoped<ISecurityService, SecurityService>();
services.AddScoped<ICookieValidatorService, CookieValidatorService>();
services.AddScoped<IDbInitializerService, DbInitializerService>();
services.AddSingleton<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
services
.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.SlidingExpiration = false;
options.LoginPath = "/";
options.LogoutPath = "/";
//options.AccessDeniedPath = new PathString("/Home/Forbidden/");
options.Cookie.Name = ".my.app1.cookie";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = context =>
{
var cookieValidatorService = context.HttpContext.RequestServices.GetRequiredService<ICookieValidatorService>();
return cookieValidatorService.ValidateAsync(context);
}
};
});
Update:(The problem solved)
I had used a class like below to get the current user.
public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
private readonly IServiceScopeFactory _scopeFactory;
public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory)
: base(loggerFactory) =>
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30);
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get the user from a new scope to ensure it fetches fresh data
var scope = _scopeFactory.CreateScope();
try
{
var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>();
return await ValidateUserAsync(userManager, authenticationState?.User);
}
finally
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal)
{
if (principal is null)
{
return false;
}
var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value;
if (!int.TryParse(userIdString, out var userId))
{
return false;
}
var user = await userManager.FindUserAsync(userId);
return user is not null;
}
}
This class is injected into the project in the following way.
services.AddSingleton<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
And the problem is exactly from here. This class should not be injected into Blazor Server project as AddSingleton
because I used the .cshtml
file to log in and logout. If I change it to AddScoped
the problem will be solved. You can see the reason for this here. However, The previous problem that was in this question came back again :((.