I have a solution in .NET 7 that works ok with, however I want to migrate to .NET 8 with new structure that MS presented and I am facing a lot of issues as authentication does not work the way I would like to.
So in AuthService I have a logic to get data from AD. I get there First name, Last Name, Email, Department based on the username I get in CustomServerAuthenticationStateProvider.GetAuthenticationStateAsync. With this approach I get SSO.
If user has not been on site yet I register him and create a record in database where I am later on able to assign him a proper roles that he needs for his work. If his username is already found in database then I just read his roles and assing them to ClaimsIdentity -> userWinIdentity.AddClaims(_userService.GetRolesClaims());
So I migrated everyting to new .NET8 solution and it works if I come to the page with clicking to link from main menu that navigates user to Profile.razor page. When I am there and press F5 to refresh the page then I get this error:
Access to host localhost is denied.
You do not have user rights to view this page.
HTTP 403 ERROR
Program.cs
using BlazorServer.Data;
using BlazorServer.Providers;
using BlazorServer.Services;
using BlazorServer.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
using MudBlazor.Services;
using BlazorServer.Consts;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddDbContext<DataContextInfor>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("InforConnection"));
});
builder.Services.AddDbContext<DataContextSpica>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("SpicaConnection"));
});
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddControllers();
builder.Services.AddLocalization();
builder.Services.AddMudServices();
builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<RoleService>();
builder.Services.AddScoped<TimeZoneService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomServerAuthenticationStateProvider>();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// 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.UseHttpsRedirection();
app.UseRouting();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.MapControllers();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(CultureConsts.supportedCultures[0])
.AddSupportedCultures(CultureConsts.supportedCultures)
.AddSupportedUICultures(CultureConsts.supportedCultures);
app.UseRequestLocalization(localizationOptions);
app.Run();
CustomServerAuthenticationStateProvider.cs
public class CustomServerAuthenticationStateProvider : AuthenticationStateProvider, IHostEnvironmentAuthenticationStateProvider
{
private readonly AuthService _authService;
private readonly UserService _userService;
private readonly IHttpContextAccessor _httpContextAccessor;
private Task<AuthenticationState> _authenticationStateTask;
public CustomServerAuthenticationStateProvider(AuthService authService, UserService userService, IHttpContextAccessor httpContextAccessor)
{
_authService = authService;
_userService = userService;
_httpContextAccessor = httpContextAccessor;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var httpContext = _httpContextAccessor.HttpContext;
var userWinIdentity = (ClaimsIdentity)httpContext?.User.Identity!;
if (userWinIdentity != null && userWinIdentity.IsAuthenticated && !string.IsNullOrEmpty(userWinIdentity.Name))
{
var username = userWinIdentity.Name.Split('\').Last();
User userObj = _authService.Authenticate(username);
_userService.User = userObj;
userWinIdentity.AddClaims(_userService.AddCustomUserClaims());
userWinIdentity.AddClaims(_userService.GetRolesClaims());
}
var user = new ClaimsPrincipal(userWinIdentity);
return await Task.FromResult(new AuthenticationState(user));
}
public void SetAuthenticationState(Task<AuthenticationState> authenticationStateTask)
{
_authenticationStateTask = authenticationStateTask ?? throw new ArgumentNullException(nameof(authenticationStateTask));
NotifyAuthenticationStateChanged(_authenticationStateTask);
}
}
UserService.cs
public List<Claim> AddCustomUserClaims()
{
return
[
new Claim("FullName", User.FullName)
];
}
public List<Claim> GetRolesClaims()
{
List<Claim> claims = new();
foreach (var role in User.Roles)
claims.Add(new Claim(ClaimTypes.GroupSid, role.Name));
// if I use ClaimTypes.Role then it does not work properly on Profile.razor, I do not know why. If someone has a clue please let me know
return claims;
}
Profile.razor
@page "/profile"
@attribute [Authorize(Roles = $"{SecurityGroup.RegisteredUser}")]
@inject UserService userService
@rendermode RenderMode.InteractiveServer
<PageTitle>@localizer["Info portal"] | @localizer["Profile"]</PageTitle>
<AuthorizeView>
@if (user != null)
{
<div class="container-fluid">
<div class="row">
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-2" style="font-weight: bold;">
@localizer["Username"]
</div>
<div class="col-xs-9 col-sm-9 col-md-9 col-lg-10">
@user.Username
</div>
</div>
<div class="row">
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-2" style="font-weight: bold;">
@localizer["First name"]
</div>
<div class="col-xs-9 col-sm-9 col-md-9 col-lg-10">
@user.FirstName
</div>
</div>
<div class="row">
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-2" style="font-weight: bold;">
@localizer["Last name"]
</div>
<div class="col-xs-9 col-sm-9 col-md-9 col-lg-10">
@user.LastName
</div>
</div>
<div class="row">
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-2" style="font-weight: bold;">
@localizer["Email"]
</div>
<div class="col-xs-9 col-sm-9 col-md-9 col-lg-10">
@user.Email
</div>
</div>
<div class="row">
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-2" style="font-weight: bold;">
@localizer["Department"]
</div>
<div class="col-xs-9 col-sm-9 col-md-9 col-lg-10">
@user.Department
</div>
</div>
</div>
}
</AuthorizeView>
@code {
User user = new();
protected override async Task OnInitializedAsync()
{
user = userService.User;
}
}
User.cs
public class User
{
public User()
{
Roles = new HashSet<Role>();
}
[Key]
public int Id { get; set; }
public string Username { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string PersonalNr { get; set; } = string.Empty;
public virtual ICollection<Role> Roles { get; set; }
}
The issue is that CustomServerAuthenticationStateProvider is not triggered on page refresh.
I was checking already everywhere and checking a lot of different approaches but none of them work, so I can’t figure out what I need to do to solve that.
So any help would be greatly appreciated.
mmaestro is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.