Share user authentication state with interactive WASM components
One of the big changes with .NET 8 is that you can mix and match different render modes for components within your Blazor app.
A fairly typical scenario is that you want to run your app on the server, using static server-side rendering, but then have islands of interactivity which use one of the interactive render modes (WASM, Server, or Auto).
But what if your app uses authentication? How can you ensure the user’s current auth state is available to all your components, regardless of which render mode they’re using?
In the last article we saw one way to handle authentication, using Auth0.
With that approach users are logged in to the server app at which point a cookie is issued to indicate that they are logged in.
Now let’s say we decide to take one component and run it interactively, using Blazor WASM.
NOTE
Interactive WASM components need a client project
When you create an application using the new Blazor Web App template and enable interactivity via WASM, you’ll get two projects in your solution:
- A server project
- Client project
Any components you want to run interactively on the client (via WASM) need to go in the Client project.
You can get the source code for this example here.
First let’s make sure the client project is wired up for authentication/authorization:
Auth0BlazorDemo.Client/Program.cs
The client project needs a reference to Microsoft.AspNetCore.Components.WebAssembly.Authentication
which you can add via Nuget.
Now we can create our interactive component.
Auth0BlazorDemo.Client/Components/Pages/Weather.razor
This component will spin up using WASM, and should show different UI depending on whether the user is logged in or not.
But if we try and run our app and view this component we get an error in the console.
One or more errors occurred. (No service for type ‘Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider’ has been registered.)
The problem is the component (and the client app hosting it) have no way, currently, to determine the authenticated state of a user.
The fix is to register an instance of AuthenticationStateProvider
to fetch that state.
But where’s the user’s auth state going to come from?
We need a way to take that state from the server and get it over to the client app when someone attempts to view our interactive component.
For this we can use PersistentComponentState.
PersistentComponentState
is a mechanism whereby we can persist state when running on the server, then access that from a component running via a different render mode.
The first step is to capture user information and persist it every time the user’s auth status changes on the server.
For that we can implement a custom AuthenticationStateProvider
in the server project.
Auth0BlazorDemo/PersistingAuthenticationStateProvider.cs
We’ll get on to the actual code to persist the state in a moment, but first this wires up the mechanism to respond to auth changes.
OnAuthenticationStateChanged
will fire every time the user’s auth state changes.
There we capture the current AuthenticationState
task and store it.
We’ll be able to use that to retrieve the latest information about the user’s auth state.
In the constructor we take PersistentComponentState
and use it to register a subscription that will be fired every time Blazor decides to persist component state.
That subscription points to the OnPersistingAsync
method.
Here we’ll decide what to persist (which will then be available to components running via WASM).
By implementing IDisposable
we can ensure we properly dispose of the _authenticationStateTask
unregister the OnAuthenticationStateChanged
handler, and also dispose of the subscription we created for reacting when Blazor persists state.
To wire this up we need to register it in Program.cs.
Persisting auth state#
So how to actually persist auth state?
For that we can use the _authenticationStateTask
and retrieve the user’s details, then use PersistentComponentState
to persist that information.
Auth0 exposes the user’s name via the name
claim.
Here we take the user Id, and user name values, assign them to a new instance of a class called UserInfo
, then persist that via component state.
I’ve created the UserInfo
class in the client project for this app. That way we can reference it from both the server and client apps.
Auth0BlazorDemo.Client/UserInfo.cs
With that the user’s basic information will be persisted in PersistentComponentState
and will be available to our components running via WASM.
Now to wire up the other side of the equation, and access that state.
Retrieve the user state#
Now we need a corresponding AuthenticationState
provider over in the client project to retrieve the persisted state.
Auth0BlazorDemo.Client/PersistentAuthenticationstateProvider.cs
AuthenticationStateProvider
requires us to declare the GetAuthenticationStateAsync
method.
Here we wire that up to initially return a default AuthenticationState
for when the user isn’t authenticated.
This returns an instance of AuthenticationState
with an ‘empty’ ClaimsPrincipal, which essentially indicates that the user is unauthenticated.
In the constructor we take in PersistentComponentState
via dependency injection and use it to try and retrieve the persisted user information.
If we find state for UserInfo
we use it to return a new instance of AuthenticationState
complete with claims for the user’s Name
and NameIdentifier
.
That’s enough to indicate to Blazor that the user is authenticated.
Before we forget, we need to wire up this new AuthenticationStateProvider
in Program.cs.
Auth0BlazorDemo.Client/Program.cs
The user is authenticated#
Wit this our interactive WASM component now works, and correctly shows UI based on whether the user is authenticated or not.
To see how this actually works, if we inspect the HTML for our page (which hosts the interactive component) we can see the encoded component state in the markup (Blazor-Server-Component-State).
This is how Blazor gets state over the boundary between server and client, by encoding the state and rendering it in the markup returned from the server. The client app (Blazor WASM) then fetches and decodes this state.
What it doesn’t do#
This approach ensures we can use Blazor’s built-in auth components in interactive WASM components.
But we’re not transmitting user tokens or credentials in component state.
For that we can depend on the cookie that’s created when users authenticate with our server app.
That cookie will be included when our interactive WASM component makes calls to the server (for example, API calls to fetch data) and should be used to check user access/permissions.
In Summary#
Persistent Component State is a handy mechanism for taking state from the server and passing it to components running via Interactive WebAssembly.
Here we’ve seen how you can use it to take user information from the server and pass it to the client.
This enables us to use Blazor’s built-in auth components like AuthorizeView
in all components, irrespective of where they’re running (on the server via SSR, or interactively on the server or client via WASM).
Check out the source code for this example here.