The issue: AuthState
is always false, even after successful API auth and after HttpContext.SignInAsync
.
I'm stuck with this as I don't understand why Blazor is not updating the authState
.
Before .NET 8, I was using custom AuthProvider
to update user:
public class WebAuthStateProvider(IHttpContextAccessor httpContextAccessor)
: AuthenticationStateProvider, IAuthProvider
{
private ClaimsPrincipal user = httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(new ClaimsIdentity());
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
return Task.FromResult(new AuthenticationState(user));
}
public async Task SaveUser(IEnumerable<Claim> claims)
{
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
user = new(identity);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
}
public async Task Logout()
{
user = new ClaimsPrincipal(new ClaimsIdentity());
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
}
public void NotifyUserAuthentication()
{
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
}
Login.razor
:
private async Task LoginWithEmail()
{
errorMessage = null;
var loginRequest = new LoginRequestModel()
{
Platform = IsWeb ? "web" : "mobile",
Email = email,
Password = password
};
try
{
var baseUrl = IsWeb ? Navigation.BaseUri : "https://mydomain/";
var requestUrl = $"{baseUrl}api/v1/auth/login";
var response = await Http.PostAsJsonAsync(requestUrl, loginRequest);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
errorMessage = "Invalid email or password.";
return;
}
if (!response.IsSuccessStatusCode)
{
errorMessage = "An error occurred while logging in. Please try again.";
return;
}
var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>();
if (authResponse != null)
{
var claims = new List<Claim>
{
new(ClaimTypes.Email, authResponse.Email),
new("Avatar", authResponse.Avatar),
new("Id", authResponse.Id),
new("JWT", authResponse.Token),
};
claims.AddRange(authResponse.Roles.Select(role => new Claim(ClaimTypes.Role, role)));
var test = AuthState.User; // always null (isAuthenticated = false)
Navigation.NavigateTo("/feed", true);
}
}
catch
{
errorMessage = "An unexpected error occurred. Please try again later.";
}
}
Program.cs
:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/identity/login";
options.LogoutPath = "/identity/logout";
options.AccessDeniedPath = "/identity/access-denied";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["AuthConfiguration:jwtTokenConfig:issuer"],
ValidAudience = builder.Configuration["AuthConfiguration:jwtTokenConfig:issuer"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["AuthConfiguration:jwtTokenConfig:secret"])
)
};
})
.AddGoogle("Google", options =>
{
options.ClientId = builder.Configuration["Google:ClientId"];
options.ClientSecret = builder.Configuration["Google:ClientSecret"];
options.ClaimActions.MapJsonKey("urn:google:profile", "link");
options.ClaimActions.MapJsonKey("urn:google:image", "picture");
options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
options.SaveTokens = true;
});
builder.Services.AddAuthorization();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddHttpContextAccessor();
...
app.UseAuthentication();
app.UseAuthorization();
app.UseRouting();
App.Razor
- wrapped into CascadingAuthenticationState
.
AuthController.cs
:
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterRequestModel requestModel)
{
if (await userManager.FindByEmailAsync(requestModel.Email) != null)
return Conflict("User with this email already exists.");
var user = new UserEntity
{
UserName = SanitizeUserName(requestModel.Name),
Email = requestModel.Email,
AvatarUrl = "default-avatar.png"
};
var result = await userManager.CreateAsync(user, requestModel.Password);
if (!result.Succeeded)
return BadRequest(result.Errors);
var roles = await userManager.GetRolesAsync(user);
var token = GenerateJwtToken(user, roles);
var claims = new List<Claim>
{
new(ClaimTypes.Email, user.Email),
new("Avatar", user.AvatarUrl),
new("Id", user.Id)
};
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties { IsPersistent = true };
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new(claimsIdentity),
authProperties);
logger.LogInformation($"User registered: {requestModel.Name} {requestModel.Email}");
return Ok(new AuthResponse
{
Token = token,
Email = user.Email,
Avatar = user.AvatarUrl,
Roles = roles.ToList(),
Id = user.Id
});
}