i want add authentication to my blazor server side application. I have this code to login.
var claims = new List<Claim>
{
new Claim(type: ClaimTypes.NameIdentifier, user.Username),
new Claim(type: ClaimTypes.Name, user.Name ?? user.Username),
new Claim(type: ClaimTypes.Sid, user.ID.ToString())
};
if (user.Email != null)
claims.Add(new Claim(type: ClaimTypes.Email, user.Email));
if (user.UserRoles != null)
{
foreach (var userRole in user.UserRoles)
{
claims.Add(new Claim(type: ClaimTypes.Role, userRole.ID_Role));
}
}
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
AllowRefresh = _authModel.Value.AllowRefresh,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(_authModel.Value.LoginExpirationMinutes),
IsPersistent = input.IsPersistent
};
await _httpContextAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
Everything looks good. The code normaly runs. But when i watch to the HttpContext.User or AuthenticationStateProvider it's empty. In explorer i don't see cookie. What i do bad? Thank you very much
Is the authentication internal to your Blazor app or are you doing any API access?
Normally you hand off to an external to the SPA authentication provider who then calls back to your SPA with the HttpContext.User
set. The default AuthenticationStateProvider
then reads the user data from HttpContext.User
.
You are authenticating internally within the SPA. The startup page is loaded and HttpContext.User
already set. I'm not sure that you can reset it. You can write a custom AuthenticationStateProvider
. Take a look at my answer to question Custom AuthenticationStateProvider in blazor project doesn't work on server side which shows how to build a test AuthenticationStateProvider
. You should be able to plug this together with your code.
Here's a very dumb AuthenticationStateProvider
that provides the authentication for two fixed users. Call ChangeIdentity
from a button to switch. You don't need to try and do anything with the HttpContext
.
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Blazor.Auth.Test
{
public class DumbAuthenticationStateProvider : AuthenticationStateProvider
{
public static ClaimsPrincipal User
=> new ClaimsPrincipal(new ClaimsIdentity(UserClaims, "Dumb Auth Type"));
public static Claim[] UserClaims
=> new[]{
new Claim(ClaimTypes.Sid, "024672e0-250a-46fc-bd35-1902974cf9e1"),
new Claim(ClaimTypes.Name, "Normal User"),
new Claim(ClaimTypes.NameIdentifier, "Normal User"),
new Claim(ClaimTypes.Email, "user@user,com"),
new Claim(ClaimTypes.Role, "User")
};
public static ClaimsPrincipal Visitor
=> new ClaimsPrincipal(new ClaimsIdentity(VisitorClaims, "Dumb Auth Type"));
public static Claim[] VisitorClaims
=> new[]{
new Claim(ClaimTypes.Sid, "324672e0-250a-46fc-bd35-1902974cf9e1"),
new Claim(ClaimTypes.Name, "Visitor"),
new Claim(ClaimTypes.NameIdentifier, "Normal Visitor"),
new Claim(ClaimTypes.Email, "visitor@user,com"),
new Claim(ClaimTypes.Role, "Visitor")
};
bool _switch;
public override Task<AuthenticationState> GetAuthenticationStateAsync()
=> Task.FromResult(_switch
? new AuthenticationState(User)
: new AuthenticationState(Visitor)
);
public Task<AuthenticationState> ChangeIdentity()
{
_switch = !_switch;
var task = this.GetAuthenticationStateAsync();
this.NotifyAuthenticationStateChanged(task);
return task;
}
}
}
And the services setup in StartUp:
services.AddScoped<AuthenticationStateProvider, DumbAuthenticationStateProvider>();
services.AddAuthorizationCore();
<button class="btn btn-dark" @onclick="ChangeID">Switch</button>
@code {
[Inject] private AuthenticationStateProvider authenticationStateProvider {get; set;}
private DumbAuthenticationStateProvider myAuthenticationStateProvider => authenticationStateProvider as DumbAuthenticationStateProvider;
private async Task ChangeID()
{
await myAuthenticationStateProvider.ChangeIdentity();
}
}
First you need to ensure the new AuthenticationStateProvider
is loaded after ServerSideBlazor
(it loads ServerAuthenticationSateProvider
which overloads whatever has been loaded before) and clean up StartUp:
{
//services.AddAuthentication(
// CookieAuthenticationDefaults.AuthenticationScheme)
// .AddCookie();
// services.AddAuthorization();
//services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<DumbAuthenticationStateProvider>());
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddScoped<AuthenticationStateProvider, DumbAuthenticationStateProvider>();
services.AddAuthorizationCore();
services.AddSingleton<WeatherForecastService>();
}
Update App.razor - change out RouteView
for AuthorizeRouteView
.
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
My Test Page
@page "/fetchdata"
@using Microsoft.AspNetCore.Components.Authorization
@using WebApplication6.Data
<AuthorizeView>
<Authorized>
<div class="m-2 p-2">
<a href="Identity/Account/Manage">Hello, @context.User.Identity.Name!</a>
</div>
</Authorized>
</AuthorizeView>
<button class="btn btn-dark" @onclick="ChangeID">Switch</button>
@code {
[Inject] private AuthenticationStateProvider authenticationStateProvider { get; set; }
private DumbAuthenticationStateProvider myAuthenticationStateProvider => authenticationStateProvider as DumbAuthenticationStateProvider;
private async Task ChangeID()
{
await myAuthenticationStateProvider.ChangeIdentity();
}
}