Securing Blazor WASM with OAuth and OIDC using Identity Server
Blazor
31 Articles
Table of Contents
What we gonna do?
In this article let's learn how to secure Blazor WASM app with OAuth and OpenID Connect using Identity Server.
Why we gonna do?
Blazor WASM app runs on Client thus cannot be trusted and just like any other JavaScript apps, any authorization can be bypassed. So its very important to focus on securing the API Our Client app is talking to.
Blazor just like any other SPA Applications runs on the Client side and the most common approach is to use OAuth2 and OpenID Connect. The reason behind this tokens based engineering design is,
- No CSRF protection required for server endpoints.
- Tokens have narrower permissions.
- Tokens tend to have short life time (1 Hour).
- Tokens can be revoked if it is a reference token.
How we gonna do?
Registering the Client
The first step is to register the Blazor WASM app in the Identity Server.
In this demo, lets make use of the demo Identity Server provided by Duende. You can access the demo Identity Server at https://demo.duendesoftware.com.
The manual registration usually involves creating a Client in the Identity Server and it looks like the following:
Code Sample - Client Registration in Duende Identity Server
Configuring the Client
The next step is to install the Microsoft.AspNetCore.Components.WebAssembly.Authentication Nuget package in the Blazor WASM app. The package provides a set of code that help the app authenticate users and obtain tokens.
Now we need to include AuthenticationService.js in the index.html file as shown below. It is this service that handles the OpenID Connect flow. Our app will call the methods defined in this script to perform the authentication operations.
Code Sample - Registering Authentication Service in index.html
Authentication Page
The part of the app that does that is the authentication component which defines the routes required for handling different authentication stages. Now lets add such a page and name it as Authentication.razor. The RemoteAuthenticatorView component manages the appropriate actions at the each stages of authentication.
Code Sample - Authentication Page
Registering OIDC Services
Now we need to configure and register Authorization in the Program.cs in the blazor WASM app. We need to call AddOidcAuthentiaction and we need to use the ClientID from the above registration while configuring the blazor WASM app. The configuration looks like the following:
Code Sample - Blazor WASM OIDC Configuration
For demo purpose, I'make using the interactive.public.short ClientID from the demo Duende Identity Server. We are using code flow which means the middleware in server will execute necessary steps to enable PKCE protection.
Redirect to Login
Next, we need to add RedirectToLogin Component which will manage redirecting unauthorized users to the login page. The current URL that the user is attempting to access is maintained by so that they can be returned to that page if authentication is successful. This happens behind the scenes using Navigation History State.
Code Sample - OIDC Redirect To Login
Thats it! We have successfully secured the Blazor WASM app with OIDC using Duende Identity Server. Now if we try to login to the app, we will be redirected to the Identity Server login page and after successful login, we will be redirected back to the app.
Demo
Here is the recap of complete flow.
Initiate Login
Login Redirect
Enter Credentials
Completing Login
Initiate Log Out
Processing Log Out
End Session
Log Out Callback
Log Out Complete
This looks awesome. We have successfully secured the Blazor WASM app with OIDC using Identity Server.
Passing Access Token to API
Passing Access tokens to API can be easily implemented by using AuthorizationMessageHandler and register it with along with AddHttpClient in Program.cs.
Code Sample - Passing Access Token to API
Displaying parts of UI
Now let's see how we can shown parts of the UI to user based on authentication status. We can use AuthorizeView component to show different parts of the UI based on the authentication status. The AuthorizeView component has two child components Authorized and NotAuthorized which will be displayed based on the authentication status. I have used the exact same code above to show login button when user is not authenticated and show logout button when user is authenticated.
User Details
Now to get details of the user we can rely on context (AuthenticationState) provided by the AuthorizeView component. We can use context.User.Identity!.Name to get the name of the user. We can also use context.User.Identity!.IsAuthenticated to check if the user is authenticated or not. And we can also access the claims of the user using context.User.Claims.
This is how I display logged in user name in above demo. We can also change this to email by simply changing options.UserOptions.NameClaim in Program.cs file. This will now display email instead of name.
Code Sample - User Options Provider
Authentication State Provider
AuthenticationStateProvider is the underlying built in service that provides the current authentication state to the application. It is this that powers AuthorizeView and CascadingAuthenticationState components. However we should not use this service directly because anonymous changes in it will not notify UI components.
So the safe and better way is to get [CascadingParameter] Task<AuthenticationState> authenticationStateTask { get; set; }. Now we can use this to get the authentication state and use it in the UI components to conditionally write logic as shown below.
Code Sample - Get Authentication State
Prevent UnAuthorized Access
Now lets see how to secure pages from UnAuthorized access. We need to add [Authorize] attribute to the page or component that we want to secure. This will restrict access to the page or component to only authenticated users. However to restrict access to child components within a component or page, we need to use AuthorizeView component.
To make sure users are notified with right message we need to use AuthorizeRouteView which we need to replace the RouteView in the router. This will now show Not Authorized message to the user when they try to access a page that they are not authorized to access.
To customise the default Not Authorized message we can use NotAuthorized render fragment within the AuthorizeRouteView component. This will now show the custom message to the user when they try to access a page.
Now instead of showing custom message we can simply redirect the user to login page when they try to access a page that they are not authorized to access. This can be done by using RedirectToLogin component within the AuthorizeRouteView component. This will now redirect the user to login page when they try to access a page that they are not authorized to access.
Code Sample - Update Router with AuthorizeRouteView
Customization
We can also Customize the text showing in UI while authentication process is happening. This can be done in RemoteAuthenticatorView component by providing the LoggingIn, LogInFailed, LogOut, LogOutFailed, LogOutSucceeded RenderFragments.
You can also customise the messages displayed to the user during the authentication process. The messages can be customised by providing the AuthenticationMessage component in the App.razor file as shown below.