I have a Blazor Server application that uses MongoDB as the database so I'm trying to implement authentication with that. So I can use the <Authenticted>, <AuthorizeView Roles="admin">
and other tags like that in the razor pages.
The built-in authentication template uses SQL Server, which I don't want in this case, and there isn't a clear example of how to do it yourself with another database. Given the example Microsoft provides here
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
namespace BlazorSample.Services
{
public class CustomAuthStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "mrfibuli"),
}, "Fake authentication type");
var user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
}
}
How should one use this in an application? You obviously wouldn't hard code a single value, and value-type, as your only source of authenticating. So how should that be parameterized? With local attributes like:
Username { get; set; }
UserType { get; set; }
In which case where would you set that?
Also how would you then use this to authenticate a user? I have the class added in the startup file under the ConfigurationServices(...)
method:
...
services.AddScoped<AuthenticationStateProvider, MongoAuthenticationStateProvider>();
...
I can't figure out how to authenticate anyone. I would imagine you validate the username and password in any number of ways, then when you know it's good you go ahead and update the authentication in .NET. I was following a tutorial where they suggested something like this in the code behind:
using System;
using System.Linq;
using DocsPlatform.Services;
using System.Threading.Tasks;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Components.Authorization;
namespace DocsPlatform.Pages
{
public class LoginBase : ComponentBase
{
[CascadingParameter]
private Task<AuthenticationState> authStateTask { get; set; }
protected string username { get; set; }
protected string password { get; set; }
protected async Task LoginUser()
{
bool isValid = true;
isValid = dbService.ValidateUser(username, password);
string email = dbService.GetEmail(username);
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Email, email),
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
return NavigationManager.NavigateTo("/");
}
}
}
However, the navigation in the return doesn't work (they didn't explain how their code even compiled) and the SignInAsync() method isn't available in they way they showed. Again, I have no idea how their code compiled. So how would one normally do this?
I can't find any tutorials, examples, etc other than the hundreds of examples that just use the built-in SQL Server template. Where can one find details on how this is used? Anything other than the "use the built-in template" or a link to the documention here would be appreciated as neither explain how to do this.
[Authorize] attribute Only use [Authorize] on @page components reached via the Blazor Router. Authorization is only performed as an aspect of routing and not for child components rendered within a page. To authorize the display of specific parts within a page, use AuthorizeView instead.
The built-in authentication template uses SQL Server, which I don't want in this case, and there isn't a clear example of how to do it yourself with another database
I guess you're using the ASP.NET Core Identity, right? If you're looking for a way that uses other providers, see official docs
How should one use this in an application? You obviously wouldn't hard code a single value, and value-type, as your only source of authenticating. So how should that be parameterized?
Since you're using the Blazor Server(instead of Blazor Wasm), you don't have to custom the GetAuthenticationStateAsync()
method and then create a principal manually. There's already a built-in ServerAuthenticationStateProvider
that both inherits from AuthenticationStateProvider
and implements the IHostEnvironmentAuthenticationStateProvider
interface:
// source code of the built-in ServerAuthenticationStateProvider
public class ServerAuthenticationStateProvider : AuthenticationStateProvider, IHostEnvironmentAuthenticationStateProvider
{
private Task<AuthenticationState> _authenticationStateTask;
/// <inheritdoc />
public override Task<AuthenticationState> GetAuthenticationStateAsync()
=> _authenticationStateTask
?? throw new InvalidOperationException($"{nameof(GetAuthenticationStateAsync)} was called before {nameof(SetAuthenticationState)}.");
/// <inheritdoc />
public void SetAuthenticationState(Task<AuthenticationState> authenticationStateTask)
{
_authenticationStateTask = authenticationStateTask ?? throw new ArgumentNullException(nameof(authenticationStateTask));
NotifyAuthenticationStateChanged(_authenticationStateTask);
}
}
As you see above, the GetAuthenticationStateAsync()
will return the auth state set by IHostEnvironmentAuthenticationStateProvider
. So what you need is to inject an IHostEnvironmentAuthenticationStateProvider
and invoke IHostEnvironmentAuthenticationStateProvider::SetAuthenticationState(...)
. And finally the authentication state will be sent to Blazor <Authorize/>
automatically.
Actually, the above ServerAuthenticationStateProvider
have no idea whether the principal is still valid. So there's another built-in concrete class for you : RevalidatingServerAuthenticationStateProvider
.
The above code works for every authentication scheme, including ASP.NET Core Identity, JwtBearer, AAD, and so on. It doesn't matter what authentication schemes you're using or which database you're using. Just extends the RevalidatingServerAuthenticationStateProvider
class.
For example, if you're using the ASP.NET Core Identity ( you might see an issue related to Cookies(See this thread), it will generate a class of RevalidatingIdentityAuthenticationStateProvider
that uses UserManager<TUser>
to validate whether the principal is valid.
public class RevalidatingIdentityAuthenticationStateProvider<TUser>
: RevalidatingServerAuthenticationStateProvider where TUser : class
{
...
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get the user manager from a new scope to ensure it fetches fresh data
// use the UserManager to determine whether the current principal is still valid
Since ASP.NET Core Identity is not limited to SQL Server, the RevalidatingIdentityAuthenticationStateProvider
works fine for other databases. If you want to use MongoDB, feel free to create a custom MyMongoDbRevalidatingAuthenticationStateProvider
.
Also how would you then use this to authenticate a user
Just declare the component like this:
<AuthorizeView>
<Authorized>
...
</Authorized>
<NotAuthorized>
...
</NotAuthorized>
</AuthorizeView>
You will NOT do it manually if you're using the default RevalidatingServerAuthenticationStateProvider
. With Blazor Server Side, the authentication is done by the AuthenticationMiddleware
, and then the authentication state will be passed to <AuthorizeView/>
automatically. And when the authentication state expires, the <AuthorizeView/>
will also update automatically.
the navigation in the return doesn't work
Actually, you code should fail before it navigates:
HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
Note the SignIn() method will try to send a cookie over HTTP. However, most of the time, there's no HTTP after the connection has been set up. Actually I have answered an exactly same question several month ago.
In short:
RevalidatingServerAuthenticationStateProvider
like RevalidatingIdentityAuthenticationStateProvider
if you need.RevalidatingIdentityAuthenticationStateProvider
for you.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With