Published:

Blazor WebAssembly - Authentication Without Identity

Tutorial built with ASP.NET Core Blazor WebAssembly 3.2.1

This is a quick post to show how to implement custom authentication in ASP.NET Core Blazor without the Identity membership system.

The below code snippets are from a Blazor JWT authentication tutorial I posted recently, for the full tutorial and live demo of the code see Blazor WebAssembly - JWT Authentication Example & Tutorial.

There are only a few pieces you need to implement custom authentication without Identity in Blazor:

  • An authentication service for logging in and out, and that provides access to the currently logged in user.
  • A custom route view to guard access to authenticated routes / pages (pages decorated with the [Authorize] attribute).
  • Login and logout pages that call the authentication service.
  • Some secure pages to test out your custom authentication system.


Authentication Service without Identity

The authentication service is used to login and logout of the Blazor app, and allows access to the currently logged in user via the User property.

The Initialize() method is called from Program.cs on startup and assigns the "user" object from local storage to the User property, which enables the user to stay logged in between page refreshes and browser sessions. This couldn't be put into the constructor because getting data from local storage is an async action.

The Login() method sends the user credentials to the API via an HTTP POST request for authentication. If successful the user object (including a JWT auth token) is assigned to the User property and stored in local storage to keep the user logged in between page refreshes.

The Logout() method sets the the User property to null, removes the user object from local storage and navigates to the login page.

using BlazorApp.Models;
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;

namespace BlazorApp.Services
{
    public interface IAuthenticationService
    {
        User User { get; }
        Task Initialize();
        Task Login(string username, string password);
        Task Logout();
    }

    public class AuthenticationService : IAuthenticationService
    {
        private IHttpService _httpService;
        private NavigationManager _navigationManager;
        private ILocalStorageService _localStorageService;

        public User User { get; private set; }

        public AuthenticationService(
            IHttpService httpService,
            NavigationManager navigationManager,
            ILocalStorageService localStorageService
        ) {
            _httpService = httpService;
            _navigationManager = navigationManager;
            _localStorageService = localStorageService;
        }

        public async Task Initialize()
        {
            User = await _localStorageService.GetItem<User>("user");
        }

        public async Task Login(string username, string password)
        {
            User = await _httpService.Post<User>("/users/authenticate", new { username, password });
            await _localStorageService.SetItem("user", User);
        }

        public async Task Logout()
        {
            User = null;
            await _localStorageService.RemoveItem("user");
            _navigationManager.NavigateTo("login");
        }
    }
}


Custom App Route View

The custom AppRouteView component is used inside the root app component instead of RouteView to render the page component for the current route along with its layout.

If the page component for the route contains an authorize attribute (@attribute [Authorize]) then the user must be logged in with the AuthenticationService above, otherwise they will be redirected to the login page.

The app route view extends the built in ASP.NET Core RouteView component and uses the base class to render the page by calling base.Render(builder).

using BlazorApp.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using System;
using System.Net;

namespace BlazorApp.Helpers
{
    public class AppRouteView : RouteView
    {
        [Inject]
        public NavigationManager NavigationManager { get; set; }

        [Inject]
        public IAuthenticationService AuthenticationService { get; set; }

        protected override void Render(RenderTreeBuilder builder)
        {
            var authorize = Attribute.GetCustomAttribute(RouteData.PageType, typeof(AuthorizeAttribute)) != null;
            if (authorize && AuthenticationService.User == null)
            {
                var returnUrl = WebUtility.UrlEncode(new Uri(NavigationManager.Uri).PathAndQuery);
                NavigationManager.NavigateTo($"login?returnUrl={returnUrl}");
            }
            else
            {
                base.Render(builder);
            }
        }
    }
}


Login Page

The login page component contains a login form with username and password fields.

On valid submit the AuthenticationService.Login(model.Username, model.Password) method is called from the HandleValidSubmit() method, if login is successful the user is redirected back to the original page they were trying to access (or the home page by default).

The OnInitialized() Blazor lifecycle method is used to automatically redirect the user to the home page if they are already logged in.

@page "/login"
@using System.ComponentModel.DataAnnotations
@inject IAuthenticationService AuthenticationService
@inject NavigationManager NavigationManager

<div class="col-md-6 offset-md-3 mt-5">
    <div class="alert alert-info">
        Username: test<br />
        Password: test
    </div>
    <div class="card">
        <h4 class="card-header">Blazor WebAssembly JWT Login Example</h4>
        <div class="card-body">
            <EditForm Model="@model" OnValidSubmit="HandleValidSubmit">
                <DataAnnotationsValidator />
                <div class="form-group">
                    <label>Username</label>
                    <InputText @bind-Value="model.Username" class="form-control" />
                    <ValidationMessage For="@(() => model.Username)" />
                </div>
                <div class="form-group">
                    <label>Password</label>
                    <InputText @bind-Value="model.Password" type="password" class="form-control" />
                    <ValidationMessage For="@(() => model.Password)" />
                </div>
                <button class="btn btn-primary">
                    @if (loading) {
                        <span class="spinner-border spinner-border-sm mr-1"></span>
                    }
                    Login
                </button>
                @if (!string.IsNullOrEmpty(error)) {
                    <div class="alert alert-danger mt-3 mb-0">@error</div>
                }
            </EditForm>
        </div>
    </div>
</div>

@code {
    private Model model = new Model();
    private bool loading;
    private string error;

    protected override void OnInitialized()
    {
        // redirect to home if already logged in
        if (AuthenticationService.User != null)
        {
            NavigationManager.NavigateTo("");
        }
    }

    private async void HandleValidSubmit()
    {
        loading = true;
        try
        {
            await AuthenticationService.Login(model.Username, model.Password);
            var returnUrl = NavigationManager.QueryString("returnUrl") ?? "/";
            NavigationManager.NavigateTo(returnUrl);
        }
        catch (Exception ex)
        {
            error = ex.Message;
            loading = false;
            StateHasChanged();
        }
    }

    private class Model
    {
        [Required]
        public string Username { get; set; }

        [Required]
        public string Password { get; set; }
    }
}


Logout Page

The logout page component logs out of the example app by calling AuthenticationService.Logout() from the Blazor OnInitialized() lifecycle method. The logout method of the authentication service redirects to the user to the login page after logout.

@page "/logout"
@inject IAuthenticationService AuthenticationService

@code {
    protected override async void OnInitialized()
    {
        await AuthenticationService.Logout();
    }
}


Secure pages with the [Authorize] attribute

With the above pieces in place you can restrict access to authenticated users to any page in your Blazor app by decorating it with the authorize attribute (@attribute [Authorize]) like below.

@page "/"
@attribute [Authorize]

<h1>This is a secure page!</h1>

 

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter or GitHub to be notified when I post new content.