Digging Into Blazor - Setting Up Custom Authentication
One of the things I wanted to integrate with this project was user accounts. I am not sure that I really need it since I’m not really intending to release my card collection app as a full fledged website for the masses. But I still wanted to add it in if for no other reason than to learn.
This is another installment in my series. If you haven’t read the others you can read about my first impressions and then integrating Entity Framework.
The first thing I realized is that the architecture of Blazor doesn’t handle user authentication in quite the same way that ASP.NET web applications do.
Blazor uses SignalR extensively, and it uses it for authentication and session management as well.
I figured I’d go through what I had to do to get it working in hopes that it will help someone else.
To start, I’m using the Blazored SessionStorage library so install that.
> dotnet add package Blazored.SessionStorage
Like many of my projects, I use Microsoft’s Identity libraries to handle users. No sense in rolling your own. And fortunately, Microsoft’s package works with Blazor as well.
To set this up, I need to add Microsoft Identity. This is how it’s done in other apps as well. In Program.cs
file, (or Startup.cs
in older project templates).
By declaring IdentityRole
with <int>
, I’m telling EntityFramework (EF) to build the Role table using an integer as the ID column type.
The UserAccount table is much the same, but since I need to add values to it, I have it defined in its own class. Again, having it inherit the IdentityUser
type with <int>
will have EF use an integer for the ID column type.
This in itself doesn’t vary much from other project types so the familiarity helped.
The next thing I needed to do was add the Blazored SessionStorage library. It’s able to be done by adding it to the Program.cs
file.
Building our Custom Auth Provider
The next step was to build a custom authentication provider. Let’s call it CustomAuthenticationStateProvider
.
It will need to implement the abstract class AuthenticationStateProvider
.
I’m going to need access to both Blazor’s session storage and our UserManager
, so I’ll add them through dependency injection.
The first thing I needed to do was to override the GetAuthenticationStateAync
method. This is the key method needed for method returns the current authentication state. This method is responsible for returning the authentication state to whatever is asking for it.
The first thing this function does is pull the user ID from the session storage. We then use the ID to find the user from the database. If the user is found, we build the identity with the user information. And return it as the authentication state.
If the user isn’t found or there is no user ID in the session state then a blank identity is returned.
Applying Authentication States
Now that that’s been implemented, we can use the <AuthorizeView>
elements in our components.
To start, I needed to modify my App.razor
file to allow for handling the authentication state.
I had to encompass the entire page with the <CascadingAuthenticationState>
tag and I had to change the <RouteView>
tag in with the <AuthorizeRouteView>
tag. This will allow for the AuthenticationState to cascade down into my components.
Here’s a good example of it in use in an actual component. In the site’s nav bar, I have links to login and to register when there is no user logged in, and the user’s name and a logout button when there is an active session.
Within the <AuthorizeView>
tags, we have two tags. Anything within the <Authorized>
tags will appear when the user is logged in, anything within the <NotAuthorized>
tags will appear when the user is not logged in.
The other key is defining the Cascading Parameter in the @code
section of the component.
[CascadingParameter] private Task<AuthenticationState>? AuthenticationState { get; set; }
This is needed so the AuthorizeView
knows the current authentication state of the user.
Implementing Logging In and Logging Out
The final step I needed to do was to allow the user to log in and then log out.
Processing on the Form
The Custom Authentication State Provider I built above doesn’t actually verify the user’s credentials are complete. It just handles maintaining the session. What I needed to do was validate the user’s email and password elsewhere first.
I created a service to handle this.
This function takes an email and a password and uses the UserManager
and SignInManager
to find the user, and then verify that the password is the correct password. If the email and password match, then the UserAccount
object is returned. If the user is not found or the password is incorrect, an exception is thrown.
Let’s take a look at my login page.
The first thing I do is pass the email and password to the LoginUser
function. If the email or password is incorrect, the exception is thrown and the error message is displayed to the user. If the email and password are correct, it passes the UserAccount
object to the AuthenticateUser
function and redirects the user to the page they came from or to the homepage.
You might have noticed the EndUserSession()
function call in the nav bar, and the StartUserSession
call in the code above. These are the last two pieces in the CustomAuthenticationStateProvider
class.
Starting with the StartUserSession
, this function first puts the user’s ID in the session, then it takes the UserAccount
object and builds the ClaimsPrincipal
to be returned, notifying the site that the authentication state has changed.
The EndUserSession
function does the opposite. It removes the user’s ID from the session, returns a blank principal, and notifies the site the authentication state has changed.
That’s basically it. It’s a little more complicated than what I’m used to, but it’s also not horribly difficult to work with.
You can view the code base for my card organizer on GitHub. It’s still a work in progress, but this is currently in place and working.