PROWAREtech

articles » current » blazor » wasm » login-form

Blazor: Login Form Example

How to implement a login form or screen using Blazor WebAssembly (WASM).

This example uses .NET 8, .NET 6 and .NET Core 3.1. If using .NET 5 then follow the .NET Core 3.1 code.

This example requires using browser local storage to store user information which is implemented through dependency injection. The logic that handles the user state is implemented in the MainLayout.razor file. This is because there is no simple way for child elements to communicate with other child elements making it difficult to create a "LoginComponent" that will update all the elements on the screen. What is happening here is the MainLayout is a cascading parameter which can be access from the child pages in order to check logged in status and draw their content based on this information.

To get started, open or create a Blazor WASM project (named "LoginForm" in this case) in Visual Studio.

.NET 8 Example

The biggest changes from .NET 6 are in the use of SVG graphics for the icons!

When creating the project in Visual Studio 2022, select to create a Blazor WebAssembly Standalone App, such as is illustrated here. This will create a pure WASM web application.

.NET 8 Blazor WebAssembly Options

LocalStorage.cs

Create a new C# class file in the client root directory and name it LocalStorage.cs, then enter this code:

// LocalStorage.cs
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Utilities
{
	public interface ILocalStorage
	{
		/// <summary>
		/// Remove a key from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		public Task RemoveAsync(string key);

		/// <summary>
		/// Save a string value to browser local storage.
		/// </summary>
		/// <param name="key">The key to use to save to and retrieve from local storage.</param>
		/// <param name="value">The string value to save to local storage.</param>
		public Task SaveStringAsync(string key, string value);

		/// <summary>
		/// Get a string value from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <returns>The string previously saved to local storage.</returns>
		public Task<string> GetStringAsync(string key);

		/// <summary>
		/// Save an array of string values to browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <param name="values">The array of string values to save to local storage.</param>
		public Task SaveStringArrayAsync(string key, string[] values);

		/// <summary>
		/// Get an array of string values from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <returns>The array of string values previously saved to local storage.</returns>
		public Task<string[]> GetStringArrayAsync(string key);
	}

	public class LocalStorage : ILocalStorage
	{
		private readonly IJSRuntime jsruntime;
		public LocalStorage(IJSRuntime jSRuntime)
		{
			jsruntime = jSRuntime;
		}

		public async Task RemoveAsync(string key)
		{
			await jsruntime.InvokeVoidAsync("localStorage.removeItem", key).ConfigureAwait(false);
		}

		public async Task SaveStringAsync(string key, string value)
		{
			var compressedBytes = await Compressor.CompressBytesAsync(Encoding.UTF8.GetBytes(value));
			await jsruntime.InvokeVoidAsync("localStorage.setItem", key, Convert.ToBase64String(compressedBytes)).ConfigureAwait(false);
		}

		public async Task<string> GetStringAsync(string key)
		{
			var str = await jsruntime.InvokeAsync<string>("localStorage.getItem", key).ConfigureAwait(false);
			if (str == null)
				return null;
			var bytes = await Compressor.DecompressBytesAsync(Convert.FromBase64String(str));
			return Encoding.UTF8.GetString(bytes);
		}

		public async Task SaveStringArrayAsync(string key, string[] values)
		{
			await SaveStringAsync(key, values == null ? "" : string.Join('\0', values));
		}

		public async Task<string[]> GetStringArrayAsync(string key)
		{
			var data = await GetStringAsync(key);
			if (!string.IsNullOrEmpty(data))
				return data.Split('\0');
			return null;
		}
	}

	internal class Compressor
	{
		public static async Task<byte[]> CompressBytesAsync(byte[] bytes)
		{
			using (var outputStream = new MemoryStream())
			{
				using (var compressionStream = new GZipStream(outputStream, CompressionLevel.Optimal))
				{
					await compressionStream.WriteAsync(bytes, 0, bytes.Length);
				}
				return outputStream.ToArray();
			}
		}

		public static async Task<byte[]> DecompressBytesAsync(byte[] bytes)
		{
			using (var inputStream = new MemoryStream(bytes))
			{
				using (var outputStream = new MemoryStream())
				{
					using (var compressionStream = new GZipStream(inputStream, CompressionMode.Decompress))
					{
						await compressionStream.CopyToAsync(outputStream);
					}
					return outputStream.ToArray();
				}
			}
		}
	}
}

After adding the above code, the Visual Studio 2022 Solution Explorer should look something like this.

Solution Explorer Example

Program.cs

Modify the client-side Program.cs file to have this additional line of code:

// Blazor WASM (client-size) Program.cs file
using LoginForm;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);


builder.Services.AddSingleton<Utilities.ILocalStorage, Utilities.LocalStorage>(); // NOTE: this line is newly added


builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();

MainLayout.razor

Modify the client-side MainLayout.razor component as follows:

@* NOTE: client-side MainLayout.razor *@
@inherits LayoutComponentBase

@* NOTE: BEGIN NEW CODE *@
@inject Utilities.ILocalStorage LocalStorage
@inject NavigationManager Nav

<CascadingValue Value="this">
@* NOTE: END NEW CODE *@

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">


			@* NOTE: BEGIN NEW CODE (REPLACED THE "About" LINK) *@
			<div style="width:70px;text-align:right;display:flex;">
				@if (loggedIn)
				{
					<button class="btn btn-sm btn-primary m-1" title="Account" @onclick="GoToAccount"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-key-fill" viewBox="0 0 16 16"><path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2zM2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" /></svg></button>
					<button class="btn btn-sm btn-danger m-1" title="Logout" @onclick="Logout"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-left" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6 12.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v2a.5.5 0 0 1-1 0v-2A1.5 1.5 0 0 1 6.5 2h8A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 5 12.5v-2a.5.5 0 0 1 1 0v2z" /><path fill-rule="evenodd" d="M.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L1.707 7.5H10.5a.5.5 0 0 1 0 1H1.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3z" /></svg></button>
				}
				else
				{
					<button class="btn btn-sm btn-success m-1" title="Login" @onclick="OpenLogin"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-in-right" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6 3.5a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 0-1 0v2A1.5 1.5 0 0 0 6.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-8A1.5 1.5 0 0 0 5 3.5v2a.5.5 0 0 0 1 0v-2z" /><path fill-rule="evenodd" d="M11.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H1.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z" /></svg></button>
				}
			</div>
			@* NOTE: END NEW CODE *@


        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

@* NOTE: BEGIN NEW CODE *@
</CascadingValue>

<div class="modal-backdrop fade @(show ? "show" : "") @(display ? "d-block" : "d-none")"></div>

<div class="modal fade @(show ? "show" : "")  @(display ? "d-block" : "d-none")" tabindex="-1" role="dialog">
	<div class="modal-dialog" role="document">
		<div class="modal-content">
			<div class="modal-header">
				<h5 class="modal-title">Login Form</h5>
				<button type="button" class="close" data-dismiss="modal" aria-label="Close" @onclick="Close">
					<span aria-hidden="true">&times;</span>
				</button>
			</div>
			<div class="modal-body">
				<div class="mb-3">
					<label for="loginEmail" class="form-label">Email</label>
					<input type="email" class="form-control" id="loginEmail" placeholder="name@example.com" autocomplete="off" required @bind-value="user" />
				</div>
				<div class="mb-3">
					<label for="loginPassword" class="form-label">Password</label>
					<input type="password" class="form-control" id="loginPassword" required @bind-value="password" />
				</div>
			</div>
			<div class="modal-footer">
				<button type="button" class="btn btn-success" @onclick="Login">Login</button>
				<button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="Close">Close</button>
			</div>
		</div>
	</div>
</div>

@code {

	private bool show, display, loggedIn;
	private string? user, password;

	public string? GetUserName()
	{
		return loggedIn ? user : null;
	}

	public async Task OpenLogin()
	{
		display = true;
		await Task.Delay(100);
		show = true;
	}

	public async Task Logout()
	{
		user = null;
		loggedIn = false;
		await LocalStorage.RemoveAsync("user");
	}

	private async Task Close()
	{
		show = false;
		await Task.Delay(500);
		display = false;
	}

	private async Task Login()
	{
		if (!string.IsNullOrEmpty(user) && !string.IsNullOrWhiteSpace(password))
		{
			// NOTE: Check password here!!
			await Close();
			loggedIn = true;
			password = null;
			await LocalStorage.SaveStringAsync("user", user);
		}
	}

	protected override async Task OnInitializedAsync()
	{
		await base.OnInitializedAsync();
		user = await LocalStorage.GetStringAsync("user");
		loggedIn = !string.IsNullOrEmpty(user);
	}

	private void GoToAccount()
	{
		Nav.NavigateTo("/account");
	}
}

@* NOTE: END NEW CODE *@

Modify NavMenu.razor as follows:

@* NOTE: client-side NavMenu.razor *@
<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">LoginForm</a>
        <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>

<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="weather">
                <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
            </NavLink>
        </div>

        @* NOTE: BEGIN NEW CODE *@
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="account">
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-key-fill" viewBox="0 0 16 16"><path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2zM2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" /></svg> Account
            </NavLink>
        </div>
        @* NOTE: END NEW CODE *@

    </nav>
</div>

@code {
    private bool collapseNavMenu = true;

    private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

Account.razor

Create a new Razor page named Account.razor and add this code:

@page "/account"

<PageTitle>Account</PageTitle>

@if (!string.IsNullOrEmpty(mainLayout.GetUserName()))
{
	<h3 class="my-4">Your Account</h3>

	<p>@mainLayout.GetUserName()</p>

	<button type="button" class="btn btn-danger" @onclick="mainLayout.Logout">Logout</button>
}
else
{
	<h3 class="my-4">Please Login</h3>

	<button type="button" class="btn btn-success" @onclick="mainLayout.OpenLogin">Login</button>
}
@code {
	[CascadingParameter]
	public MainLayout mainLayout { get; set; }

	protected override async Task OnInitializedAsync()
	{
		await base.OnInitializedAsync();
	}
}

In the end, the Visual Studio 2022 Solution Explorer should look something like this.

Solution Explorer Final Example

Run the Application

Hit Ctrl+F5 to run the application and log in with any user name and password to see how access is controlled.

.NET 6 Example

LocalStorage.cs

Create a new C# class file in the client root directory and name it LocalStorage.cs, then enter this code:

// LocalStorage.cs
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Utilities
{
	public interface ILocalStorage
	{
		/// <summary>
		/// Remove a key from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		public Task RemoveAsync(string key);

		/// <summary>
		/// Save a string value to browser local storage.
		/// </summary>
		/// <param name="key">The key to use to save to and retrieve from local storage.</param>
		/// <param name="value">The string value to save to local storage.</param>
		public Task SaveStringAsync(string key, string value);

		/// <summary>
		/// Get a string value from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <returns>The string previously saved to local storage.</returns>
		public Task<string> GetStringAsync(string key);

		/// <summary>
		/// Save an array of string values to browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <param name="values">The array of string values to save to local storage.</param>
		public Task SaveStringArrayAsync(string key, string[] values);

		/// <summary>
		/// Get an array of string values from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <returns>The array of string values previously saved to local storage.</returns>
		public Task<string[]> GetStringArrayAsync(string key);
	}

	public class LocalStorage : ILocalStorage
	{
		private readonly IJSRuntime jsruntime;
		public LocalStorage(IJSRuntime jSRuntime)
		{
			jsruntime = jSRuntime;
		}

		public async Task RemoveAsync(string key)
		{
			await jsruntime.InvokeVoidAsync("localStorage.removeItem", key).ConfigureAwait(false);
		}

		public async Task SaveStringAsync(string key, string value)
		{
			var compressedBytes = await Compressor.CompressBytesAsync(Encoding.UTF8.GetBytes(value));
			await jsruntime.InvokeVoidAsync("localStorage.setItem", key, Convert.ToBase64String(compressedBytes)).ConfigureAwait(false);
		}

		public async Task<string> GetStringAsync(string key)
		{
			var str = await jsruntime.InvokeAsync<string>("localStorage.getItem", key).ConfigureAwait(false);
			if (str == null)
				return null;
			var bytes = await Compressor.DecompressBytesAsync(Convert.FromBase64String(str));
			return Encoding.UTF8.GetString(bytes);
		}

		public async Task SaveStringArrayAsync(string key, string[] values)
		{
			await SaveStringAsync(key, values == null ? "" : string.Join('\0', values));
		}

		public async Task<string[]> GetStringArrayAsync(string key)
		{
			var data = await GetStringAsync(key);
			if (!string.IsNullOrEmpty(data))
				return data.Split('\0');
			return null;
		}
	}

	internal class Compressor
	{
		public static async Task<byte[]> CompressBytesAsync(byte[] bytes)
		{
			using (var outputStream = new MemoryStream())
			{
				using (var compressionStream = new GZipStream(outputStream, CompressionLevel.Optimal))
				{
					await compressionStream.WriteAsync(bytes, 0, bytes.Length);
				}
				return outputStream.ToArray();
			}
		}

		public static async Task<byte[]> DecompressBytesAsync(byte[] bytes)
		{
			using (var inputStream = new MemoryStream(bytes))
			{
				using (var outputStream = new MemoryStream())
				{
					using (var compressionStream = new GZipStream(inputStream, CompressionMode.Decompress))
					{
						await compressionStream.CopyToAsync(outputStream);
					}
					return outputStream.ToArray();
				}
			}
		}
	}
}

Program.cs

Modify the client-side Program.cs file to have this additional line of code:

// Blazor WASM (client-size) Program.cs file
using LoginForm.Client;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);


builder.Services.AddSingleton<Utilities.ILocalStorage, Utilities.LocalStorage>(); // NOTE: this line is newly added


builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();

MainLayout.razor

Modify MainLayout.razor as follows:

@inherits LayoutComponentBase

@* NOTE: BEGIN NEW CODE *@
@inject Utilities.ILocalStorage LocalStorage

<CascadingValue Value="this">
@* NOTE: END NEW CODE *@

<div class="page">
	<div class="sidebar">
		<NavMenu />
	</div>

	<main>
		<div class="top-row px-4">


			@* NOTE: BEGIN NEW CODE (REPLACED THE "About" LINK) *@
			<div style="width:70px;text-align:right;">
				@if (loggedIn)
				{
					<a class="btn btn-sm btn-primary text-white" title="Account" href="account"><span class="oi oi-key"></span></a>
					<button class="btn btn-sm btn-danger" title="Logout" @onclick="Logout"><span class="oi oi-account-logout"></span></button>
				}
				else
				{
					<button class="btn btn-sm btn-success" title="Login" @onclick="OpenLogin"><span class="oi oi-account-login"></span></button>
				}
			</div>
			@* NOTE: END NEW CODE *@


		</div>

		<article class="content px-4">
			@Body
		</article>
	</main>
</div>

@* NOTE: BEGIN NEW CODE *@
</CascadingValue>
@* NOTE: END NEW CODE *@


@* NOTE: BEGIN NEW CODE *@

<div class="modal-backdrop fade @(show ? "show" : "") @(display ? "d-block" : "d-none")"></div>

<div class="modal fade @(show ? "show" : "")  @(display ? "d-block" : "d-none")" tabindex="-1" role="dialog">
	<div class="modal-dialog" role="document">
		<div class="modal-content">
			<div class="modal-header">
				<h5 class="modal-title">Login Form</h5>
				<button type="button" class="close" data-dismiss="modal" aria-label="Close" @onclick="Close">
					<span aria-hidden="true">&times;</span>
				</button>
			</div>
			<div class="modal-body">
				<div class="mb-3">
					<label for="loginEmail" class="form-label">Email</label>
					<input type="email" class="form-control" id="loginEmail" placeholder="name@example.com" autocomplete="off" required @bind-value="user" />
				</div>
				<div class="mb-3">
					<label for="loginPassword" class="form-label">Password</label>
					<input type="password" class="form-control" id="loginPassword" required />
				</div>
			</div>
			<div class="modal-footer">
				<button type="button" class="btn btn-success" @onclick="Login">Login</button>
				<button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="Close">Close</button>
			</div>
		</div>
	</div>
</div>

@* NOTE: END NEW CODE *@


@* NOTE: BEGIN NEW CODE *@

@code {

	private bool show, display, loggedIn;
	private string? user;

	public string? GetUserName()
	{
		return loggedIn ? user : null;
	}

	public async Task OpenLogin()
	{
		display = true;
		await Task.Delay(100);
		show = true;
	}

	public async Task Logout()
	{
		user = null;
		loggedIn = false;
		await LocalStorage.RemoveAsync("user");
	}

	private async Task Close()
	{
		show = false;
		await Task.Delay(500);
		display = false;
	}

	private async Task Login()
	{
		if (!string.IsNullOrEmpty(user))
		{
			await Close();
			loggedIn = true;
			await LocalStorage.SaveStringAsync("user", user);
		}
	}

	protected override async Task OnInitializedAsync()
	{
		await base.OnInitializedAsync();
		user = await LocalStorage.GetStringAsync("user");
		loggedIn = !string.IsNullOrEmpty(user);
	}
}

@* NOTE: END NEW CODE *@

Modify NavMenu.razor as follows:

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">LoginForm</a>
        <button class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </div>
		@* NOTE: BEGIN NEW CODE *@
		<div class="nav-item px-3">
			<NavLink class="nav-link" href="account">
				<span class="oi oi-key" aria-hidden="true"></span> Account
			</NavLink>
		</div>
		@* NOTE: END NEW CODE *@
    </nav>
</div>

@code {
    private bool collapseNavMenu = true;

    private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

Account.razor

Create a new Razor page named Account.razor and add this code:

@page "/account"

<PageTitle>Account</PageTitle>

@if (!string.IsNullOrEmpty(mainLayout.GetUserName()))
{
	<h3 class="my-4">Your Account</h3>

	<p>@mainLayout.GetUserName()</p>

	<button type="button" class="btn btn-danger" @onclick="mainLayout.Logout">Logout</button>
}
else
{
	<h3 class="my-4">Please Login</h3>

	<button type="button" class="btn btn-success" @onclick="mainLayout.OpenLogin">Login</button>
}
@code {
	[CascadingParameter]
	public MainLayout mainLayout { get; set; }

	protected override async Task OnInitializedAsync()
	{
		await base.OnInitializedAsync();
	}
}

Run the Application

Run the application and log in with any user name to see how access is controlled.

.NET Core 3.1 Example

LocalStorage.cs

Create a new C# class file in the client root directory and name it LocalStorage.cs, then enter this code:

// LocalStorage.cs
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Utilities
{
	public interface ILocalStorage
	{
		/// <summary>
		/// Remove a key from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		public Task RemoveAsync(string key);

		/// <summary>
		/// Save a string value to browser local storage.
		/// </summary>
		/// <param name="key">The key to use to save to and retrieve from local storage.</param>
		/// <param name="value">The string value to save to local storage.</param>
		public Task SaveStringAsync(string key, string value);

		/// <summary>
		/// Get a string value from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <returns>The string previously saved to local storage.</returns>
		public Task<string> GetStringAsync(string key);

		/// <summary>
		/// Save an array of string values to browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <param name="values">The array of string values to save to local storage.</param>
		public Task SaveStringArrayAsync(string key, string[] values);

		/// <summary>
		/// Get an array of string values from browser local storage.
		/// </summary>
		/// <param name="key">The key previously used to save to local storage.</param>
		/// <returns>The array of string values previously saved to local storage.</returns>
		public Task<string[]> GetStringArrayAsync(string key);
	}

	public class LocalStorage : ILocalStorage
	{
		private readonly IJSRuntime jsruntime;
		public LocalStorage(IJSRuntime jSRuntime)
		{
			jsruntime = jSRuntime;
		}

		public async Task RemoveAsync(string key)
		{
			await jsruntime.InvokeVoidAsync("localStorage.removeItem", key).ConfigureAwait(false);
		}

		public async Task SaveStringAsync(string key, string value)
		{
			var compressedBytes = await Compressor.CompressBytesAsync(Encoding.UTF8.GetBytes(value));
			await jsruntime.InvokeVoidAsync("localStorage.setItem", key, Convert.ToBase64String(compressedBytes)).ConfigureAwait(false);
		}

		public async Task<string> GetStringAsync(string key)
		{
			var str = await jsruntime.InvokeAsync<string>("localStorage.getItem", key).ConfigureAwait(false);
			if (str == null)
				return null;
			var bytes = await Compressor.DecompressBytesAsync(Convert.FromBase64String(str));
			return Encoding.UTF8.GetString(bytes);
		}

		public async Task SaveStringArrayAsync(string key, string[] values)
		{
			await SaveStringAsync(key, values == null ? "" : string.Join('\0', values));
		}

		public async Task<string[]> GetStringArrayAsync(string key)
		{
			var data = await GetStringAsync(key);
			if (!string.IsNullOrEmpty(data))
				return data.Split('\0');
			return null;
		}
	}

	internal class Compressor
	{
		public static async Task<byte[]> CompressBytesAsync(byte[] bytes)
		{
			using (var outputStream = new MemoryStream())
			{
				using (var compressionStream = new GZipStream(outputStream, CompressionLevel.Optimal))
				{
					await compressionStream.WriteAsync(bytes, 0, bytes.Length);
				}
				return outputStream.ToArray();
			}
		}

		public static async Task<byte[]> DecompressBytesAsync(byte[] bytes)
		{
			using (var inputStream = new MemoryStream(bytes))
			{
				using (var outputStream = new MemoryStream())
				{
					using (var compressionStream = new GZipStream(inputStream, CompressionMode.Decompress))
					{
						await compressionStream.CopyToAsync(outputStream);
					}
					return outputStream.ToArray();
				}
			}
		}
	}
}

Program.cs

Modify the client-side Program.cs file to have this additional line of code:

// Blazor WASM (client-size) Program.cs file
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace LoginForm.Client
{
	public class Program
	{
		public static async Task Main(string[] args)
		{
			var builder = WebAssemblyHostBuilder.CreateDefault(args);


			builder.Services.AddSingleton<Utilities.ILocalStorage, Utilities.LocalStorage>(); // NOTE: this line is newly added


			builder.RootComponents.Add<App>("app");

			builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

			await builder.Build().RunAsync();
		}
	}
}

MainLayout.razor

Modify MainLayout.razor as follows:

@inherits LayoutComponentBase

@* NOTE: BEGIN NEW CODE *@
@inject Utilities.ILocalStorage LocalStorage

<CascadingValue Value="this">
@* NOTE: END NEW CODE *@

	<div class="sidebar">
		<NavMenu />
	</div>

	<div class="main">
		<div class="top-row px-4">


			@* NOTE: BEGIN NEW CODE (REPLACED THE "About" LINK) *@
			<div style="width:70px;text-align:right;">
				@if (loggedIn)
				{
					<a class="btn btn-sm btn-primary text-white" title="Account" href="account"><span class="oi oi-key"></span></a>
					<button class="btn btn-sm btn-danger" title="Logout" @onclick="Logout"><span class="oi oi-account-logout"></span></button>
				}
				else
				{
					<button class="btn btn-sm btn-success" title="Login" @onclick="OpenLogin"><span class="oi oi-account-login"></span></button>
				}
			</div>
			@* NOTE: END NEW CODE *@


		</div>

		<div class="content px-4">
			@Body
		</div>
	</div>

@* NOTE: BEGIN NEW CODE *@
</CascadingValue>
@* NOTE: END NEW CODE *@


@* NOTE: BEGIN NEW CODE *@

<div class="modal-backdrop fade @(show ? "show" : "") @(display ? "d-block" : "d-none")"></div>

<div class="modal fade @(show ? "show" : "")  @(display ? "d-block" : "d-none")" tabindex="-1" role="dialog">
	<div class="modal-dialog" role="document">
		<div class="modal-content">
			<div class="modal-header">
				<h5 class="modal-title">Login Form</h5>
				<button type="button" class="close" data-dismiss="modal" aria-label="Close" @onclick="Close">
					<span aria-hidden="true">&times;</span>
				</button>
			</div>
			<div class="modal-body">
				<div class="mb-3">
					<label for="loginEmail" class="form-label">Email</label>
					<input type="email" class="form-control" id="loginEmail" placeholder="name@example.com" autocomplete="off" required @bind-value="user" />
				</div>
				<div class="mb-3">
					<label for="loginPassword" class="form-label">Password</label>
					<input type="password" class="form-control" id="loginPassword" required />
				</div>
			</div>
			<div class="modal-footer">
				<button type="button" class="btn btn-success" @onclick="Login">Login</button>
				<button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="Close">Close</button>
			</div>
		</div>
	</div>
</div>

@* NOTE: END NEW CODE *@


@* NOTE: BEGIN NEW CODE *@

@code {

	private bool show, display, loggedIn;
	private string user;

	public string GetUserName()
	{
		return loggedIn ? user : null;
	}

	public async Task OpenLogin()
	{
		display = true;
		await Task.Delay(100);
		show = true;
	}

	public async Task Logout()
	{
		user = null;
		loggedIn = false;
		await LocalStorage.RemoveAsync("user");
	}

	private async Task Close()
	{
		show = false;
		await Task.Delay(500);
		display = false;
	}

	private async Task Login()
	{
		if (!string.IsNullOrEmpty(user))
		{
			await Close();
			loggedIn = true;
			await LocalStorage.SaveStringAsync("user", user);
		}
	}

	protected override async Task OnInitializedAsync()
	{
		await base.OnInitializedAsync();
		user = await LocalStorage.GetStringAsync("user");
		loggedIn = !string.IsNullOrEmpty(user);
	}
}

@* NOTE: END NEW CODE *@

Modify NavMenu.razor as follows:

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">LoginForm</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
	<ul class="nav flex-column">
		<li class="nav-item px-3">
			<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
				<span class="oi oi-home" aria-hidden="true"></span> Home
			</NavLink>
		</li>
		<li class="nav-item px-3">
			<NavLink class="nav-link" href="counter">
				<span class="oi oi-plus" aria-hidden="true"></span> Counter
			</NavLink>
		</li>
		<li class="nav-item px-3">
			<NavLink class="nav-link" href="fetchdata">
				<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
			</NavLink>
		</li>
		@* NOTE: BEGIN NEW CODE *@
		<li class="nav-item px-3">
			<NavLink class="nav-link" href="account">
				<span class="oi oi-key" aria-hidden="true"></span> Account
			</NavLink>
		</li>
		@* NOTE: END NEW CODE *@
	</ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

Account.razor

Create a new Razor page named Account.razor and add this code:

@page "/account"

@if (!string.IsNullOrEmpty(mainLayout.GetUserName()))
{
	<h3 class="my-4">Your Account</h3>

	<p>@mainLayout.GetUserName()</p>

	<button type="button" class="btn btn-danger" @onclick="mainLayout.Logout">Logout</button>
}
else
{
	<h3 class="my-4">Please Login</h3>

	<button type="button" class="btn btn-success" @onclick="mainLayout.OpenLogin">Login</button>
}
@code {
	[CascadingParameter]
	public MainLayout mainLayout { get; set; }

	protected override async Task OnInitializedAsync()
	{
		await base.OnInitializedAsync();
	}
}

Run the Application

Run the application and log in with any user name to see how access is controlled.

Coding Video

https://youtu.be/UtYjWbGG3A0

JWT Authentication

Next, to add JSON Web Token authentication to the Blazor application, see this article.


This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
CLOSE