I have a complete custom implementation of JWT auth for Blazor Wasm + Web Api. I would like to make it work with Blazor Server (with the proper modifications).
1, Startup/Program
// I tried with every combinations
//services.AddAuthenticationCore();
//services.AddAuthorizationCore();
//services.AddAuthentication();
//services.AddAuthorization();
services.AddScoped<CustomAuthenticationStateProvider>();
services.AddScoped<ServerAuthenticationStateProvider>(provider =>
provider.GetRequiredService<CustomAuthenticationStateProvider>());
...
app.UseRouting();
//app.UseAuthentication();
//app.UseAuthorization();
app.MapBlazorHub();
2, App.razor
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<div class="d-flex flex-column mx-auto">
<h1 class="mx-auto">Sorry</h1>
<p class="mx-auto">You're not authorized to reach this resource.</p>
<p class="mx-auto">Please log in or return to the index page</p>
<UnauthorizedNavigation />
</div>
</NotAuthorized>
<Authorizing>
<h1>Authentication in progress</h1>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
3, CustomAuthenticationStateProvider implementation
public class CustomAuthenticationStateProvider : ServerAuthenticationStateProvider
{
private readonly ILocalTokenService _localTokenService;
private readonly JwtTokenService _jwtTokenService;
#region Constructor & Initialization
public CustomAuthenticationStateProvider(ILocalTokenService localTokenService, JwtTokenService jwtTokenService)
{
_localTokenService = localTokenService;
_jwtTokenService = jwtTokenService;
}
#endregion
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
await Task.Delay(1000);
Debug.WriteLine("GetAuthenticationStateAsync");
try
{
// ...Code...
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName)
}, auth_type);
var principal = new ClaimsPrincipal(identity);
var authTask = Task.FromResult(new AuthenticationState(principal));
return await authTask;
}
else
{
Debug.WriteLine("Empty");
return await ReturnEmptyAuthState();
}
}
catch (Exception e)
{
Debug.WriteLine(e);
_logger.LogError(e.Message);
return await ReturnEmptyAuthState();
}
}
private async Task<AuthenticationState> ReturnEmptyAuthState()
{
var identityEmpty = new ClaimsIdentity();
var principalEmpty = new ClaimsPrincipal(identityEmpty);
var authTaskEmpty = Task.FromResult(new AuthenticationState(principalEmpty));
return await authTaskEmpty;
}
public async Task<bool> MarkUserAsAuthenticated(string jwtToken, string refreshToken)
{
try
{
// ...Code...
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName)
}, auth_type);
var userclaim = new ClaimsPrincipal(identity);
var task = Task.FromResult(new AuthenticationState(userclaim));
SetAuthenticationState(task);
//NotifyAuthenticationStateChanged(task);
return true;
}
catch (Exception e)
{
_logger.LogError(e.Message);
return false;
}
}
public async Task MarkUserAsLoggedOut()
{
// ...Code...
SetAuthenticationState(task);
//NotifyAuthenticationStateChanged(task);
}
}
Issues (after successfull login!):
a, I created an AuthTestPage with @attribute [Authorize] - it always displays as not authorized
b, Using AuthorizeView - Authorized/NotAuthorized parts - same, the Authorized part is never triggered
The source of the issue I guess that the CustomAuthenticationStateProvider.GetAuthenticationStateAsync is never triggered by the framework for some reason. It should be called at least on new page navigation or by opening the app in browser I suspect.
What do you think, what am I missing, or it is a bad approach with Blazor Server what I try to achieve?