53

Part-1 Blazor WebAssembly Cookie Authentication[.NET 6]

 2 years ago
source link: https://www.learmoreseekmore.com/2022/04/blazorwasm-cookie-series-part-1-blazor-webassembly-cookie-authentication.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
In this article, we are going to authenticate our Blazor WebAssemably application with simple Asp.NetCore cookie.

Targets Of Blazor WebAssembly Cookie Auth Series:

Let's have look at this Blazor WebAssmbly series:
  • Part -1 is simple asp.net core cookie authentication.
  • Part-2 is cookie authentication with FaceBook.
  • Part-3 is cookie authentication with Twitter.
  • Part-4 is cookie authentication with Google.
  • Part-5 is cookie authentication with Microsoft.

Blazor WebAssembly Cookie Authentication Flow:

  • We must have 2 applications 'Blazor WebAssembly Application'(which runs on the user browser), and 'Asp.Net Core API'(which runs on the server).
  • Blazor WebAssembly requests the API for user authentication by sending the user credentials to API.
  • For valid credentials, API generates auth cookie and sends it to the client application.
  • So after successful authentication of every request from Blazor WebAssembly to API, the auth cookie will be attached to every subsequent request to make the request as authenticated.

Create A Sample User Table:

Let's run the below SQL script to create a sample user table.
  1. CREATE TABLE [dbo].[User](
  2. [Id] [int] IDENTITY(1,1) NOT NULL,
  3. [Email] [varchar](200) NULL,
  4. [FirstName] [varchar](100) NULL,
  5. [LastName] [varchar](100) NULL,
  6. [Password] [varchar](200) NULL,
  7. [ExternalLoginName] [varchar](50) NULL,
  8. CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
  9. [Id] ASC
  10. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  11. ) ON [PRIMARY]

Create A .NET6 Web API Application:

Let's create a .Net6 Web API sample application to accomplish our demo. We can use either Visual Studio 2022 or Visual Studio Code(using .NET CLI commands) to create any .NET6 application. For this demo, I'm using the 'Visual Studio Code'(using the .NET CLI command) editor.
.NET CLI Command:
dotnet new webapi -o Your_Project_Name

Install EntityFramework Core NuGet Package Into API Project:

Now install the following package into the API project.
Let's install the Entity Framework Core NuGet package.
Package Manager Command:
Install-Package Microsoft.EntityFrameworkCore -Version 6.0.3
.NET CLI Command:
dotnet add package Microsoft.EntityFrameworkCore --version 6.0.3
Let's install the Entity Framework Core SQL NuGet package.
Package Manager Command:
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 6.0.3
.NET CLI Command:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 6.0.3

Configure Database Conext In API App:

Let's create an entity of our table like 'User.cs'.
API_Project/Data/Entities/Users.cs:
  1. namespace CookieApi.Data.Entities;
  2. public class User
  3. public int Id { get; set; }
  4. public string? Email { get; set; }
  5. public string? FirstName { get; set; }
  6. public string? LastName { get; set; }
  7. public string? Password{get;set;}
  8. public string? ExternalLoginName{get;set;}
Now let's create a Database Context entity like 'CookieAuthContext.cs'.
API_Project/Data/CookieAuthContext.cs:
  1. using CookieApi.Data.Entities;
  2. using Microsoft.EntityFrameworkCore;
  3. namespace CookieApi.Data;
  4. public class CookieAuthContext:DbContext
  5. public CookieAuthContext(DbContextOptions<CookieAuthContext> option):base(option)
  6. public DbSet<User> User {get;set;}
  • Inside the Database context, we need to register our table classes like we registered our 'User'.
Add the database connection string in 'appsettings.Development.json'.
API_Project/appsettings.Development.json:
  1. "ConnectionStrings": {
  2. "CookieAuthConnection":""
Now register our database context entity in 'Program.cs'.
API_Project/Program.cs:
  1. builder.Services.AddDbContext<CookieAuthContext>(options => {
  2. options.UseSqlServer(builder.Configuration.GetConnectionString("CookieAuthConnection"));

Create A AuthController In API Project:

Let's create a 'AuthController' in our API Project. In this controller, we are going to add our action methods like 'Login', 'Logout', 'UserProfile'.
API_Project/Controllers/AuthController.cs:
  1. using CookieApi.Data;
  2. using Microsoft.AspNetCore.Mvc;
  3. namespace CookieApi.Controllers;
  4. [ApiController]
  5. [Route("[controller]")]
  6. public class AuthController:ControllerBase
  7. private readonly CookieAuthContext _cookieAuthContext;
  8. public AuthController(CookieAuthContext cookieAuthContext)
  9. _cookieAuthContext = cookieAuthContext;
  • Here injected our 'CookieAuthContext' into the 'AuthController'.

Add User Login Endpoint In API Project:

Let's create DTO(data transfer object) for our HTTP POST login endpoint like 'LoginDto.cs'.
API_Project/Dtos/LoginDto.cs:
  1. namespace CookieApi.Dtos;
  2. public class LoginDto
  3. public string Email { get; set; }
  4. public string Password { get; set; }
Note: In this demo, I'm going to store the raw password in the table which is very bad practice, we have to hash the password stored into the database. For custom hashing, you can refer to my article.
Now let's add our 'LoginAsync' action method into the 'AuthController'.
API_Project/Controllers/AuthController.cs:
  1. using System.Security.Claims;
  2. using CookieApi.Data;
  3. using CookieApi.Dtos;
  4. using Microsoft.AspNetCore.Authentication;
  5. using Microsoft.AspNetCore.Authentication.Cookies;
  6. using Microsoft.AspNetCore.Mvc;
  7. using Microsoft.EntityFrameworkCore;
  8. // code hidden for display purpose
  9. [HttpPost]
  10. [Route("login")]
  11. public async Task<IActionResult> LoginAsync(LoginDto login)
  12. var user = await _cookieAuthContext
  13. .User.Where(_ => _.Email.ToLower() == login.Email.ToLower() &&
  14. _.Password == login.Password && _.ExternalLoginName == null)
  15. .FirstOrDefaultAsync();
  16. if (user == null)
  17. return BadRequest("Invalid Credentials");
  18. var claims = new List<Claim>
  19. new Claim("userid", user.Id.ToString()),
  20. new Claim(ClaimTypes.Email, user.Email)
  21. var claimsIdentity = new ClaimsIdentity(
  22. claims, CookieAuthenticationDefaults.AuthenticationScheme);
  23. var authProperties = new AuthenticationProperties();
  24. await HttpContext.SignInAsync(
  25. CookieAuthenticationDefaults.AuthenticationScheme,
  26. new ClaimsPrincipal(claimsIdentity),
  27. authProperties);
  28. return Ok("Success");
  • (Line: 15-18) Fetching the user from the database using 'Email', 'Password. Here we check 'ExternalLoginName' for 'null' which means it is a user email and password authentication because users can have different authentication like 'Google', 'Facebook', etc with the same email address.
  • (Line: 19-22) User credentials are invalid then we return as a bad request.
  • (Line: 24-28) Defining authenticated user claims that will be stored in the authentication cookie.
  • (Line: 31&32) Initialized 'ClaimsIdentity' that need 'User Claims' and 'AuthenticationType'.
  • (Line: 35-38) Invoking the 'HttpContext.SignInAsync()' method that generates the authentication cookie.

Add User Logout Endpoint In API Project:

Let's add HTTP Post 'LogoutAsync' action method into the 'AuthController'.
API_Project/Controllers/AuthController.cs:
  1. [HttpPost]
  2. [Route("logout")]
  3. public async Task<IActionResult> LogoutAsync()
  4. await HttpContext.SignOutAsync();
  5. return Ok("success");
  • (Line: 5) Invokes the 'HttpContext.SingOutAsync()' method to logout the user by clearing the authenticated user cookie.

Add UserProfile Auth Protection Endpoint In API Project:

Let's create a response DTO for our user profile endpoint like 'UserProfileDto.cs'.
API_Projects/Dtos/UserProfileDto.cs:
  1. namespace CookieApi.Dtos;
  2. public class UserProfileDto
  3. public int UserId { get; set; }
  4. public string Email { get; set; }
  5. public string FirstName { get; set; }
  6. public string LastName { get; set; }
Let's add authentication-protected action methods like 'UserProfileAsync' in 'AuthController'.
API_Project/Controllers/AuthController.cs:
  1. [Authorize]
  2. [HttpGet]
  3. [Route("user-profile")]
  4. public async Task<IActionResult> UserProfileAsync()
  5. int userId =HttpContext.User.Claims
  6. .Where(_ => _.Type =="userid")
  7. .Select(_ =>Convert.ToInt32( _.Value))
  8. .First();
  9. var userProfile = await _cookieAuthContext
  10. .User
  11. .Where(_ => _.Id == userId)
  12. .Select(_ => new UserProfileDto{
  13. UserId = _.Id,
  14. Email = _.Email,
  15. FirstName = _.FirstName,
  16. LastName = _.LastName
  17. }).FirstOrDefaultAsync();
  18. return Ok(userProfile);
  • (Line: 1) Our 'user-profile' endpoint is protected by user authentication by enabling the 'Authorize' attribute.
  • (Line: 6-9) Fetching the authenticated user id claim from the requests HttpContext.
  • (Line: 11-19) Defining the user profile information based on the authenticated user.

Configure Required Services And Middlewares In API Project:

Our API and Blazor Webassembly(application going to be created in upcoming steps) application run on different domains so we have to enable the 'Cors' service.
API_Project/Program.cs:
  1. builder.Services.AddAuthentication(
  2. CookieAuthenticationDefaults.AuthenticationScheme
  3. .AddCookie();
  4. builder.Services.AddCors(options =>
  5. options.AddPolicy("CorsSpecs",
  6. builder =>
  7. builder
  8. .AllowAnyHeader()
  9. .AllowAnyMethod()
  10. .SetIsOriginAllowed(options => true)
  11. .AllowCredentials();
  12. // Above the below line register Cors
  13. var app = builder.Build();
  • (Line: 1-4) Registered cookie authentication.
  • (Line: 7) Defined name to our 'Cors' service.
  • (Line: 11&12) Allowing all 'Headers' and 'Methods'.
  • (Line: 13&14) In our blazor application after the successful authentication, every other request carries auth cookie along with it. To accept auth cookie from a request from a different domain must add the 'AllowCredentials()' method. That 'AllowCredentials()' method fails to work with 'AllowAnyOrigin()' method, so we have to configure 'SetIsOringAllowed(option => true)' if we use 'AllowedCredentials()'.
Now let's add 'Cors' and 'Authentication' middlewares.
API_Project/Program.cs:
  1. app.UseCors("CorsSpecs");
  2. app.UseAuthentication();
  3. //just above the 'UseAuthorization'
  4. //middleware add our required middlewares
  5. app.UseAuthorization();

Create A .NET6 Blazor WebAssembly Application:

Let's create a .Net6 Blazor WebAssembly sample application to accomplish our demo. We can use either Visual Studio 2022 or Visual Studio Code(using .NET CLI commands) to create any .NET6 application. For this demo, I'm using the 'Visual Studio Code'(using the .NET CLI command) editor.
.NET CLI Command:
dotnet new blazorwasm -o Your_Project_Name

Install Required Packages In The BlazorWasm App:

Let's install the Blazor WebAssembly Authentication library.
Package Manager:
Install-Package Microsoft.AspNetCore.Components.WebAssembly.Authentication -Version 6.0.3
.NET CLI Command:
dotnet add package Microsoft.AspNetCore.Components.WebAssembly.Authentication --version 6.0.3
Let's install the 'Microsoft.Extensions.Http' library.
Package Manager:
Install-Package Microsoft.Extensions.Http -Version 6.0.0
.NET CLI Command:
dotnet add package Microsoft.Extensions.Http --version 6.0.0
Let's install the 'Blazored.LocalStorage' library.
Package Manager:
Install-Package Blazored.LocalStorage -Version 4.2.0
.NET CLI Command:
dotnet add package Blazored.LocalStorage --version 4.2.0

Register Installed Packages Services In Blazor APP:

Let's add the below namespaces to the '_Imports.razor'
BlazorWasm_Project/_Imports.razor:
  1. @using Microsoft.AspNetCore.Authorization
  2. @using Microsoft.AspNetCore.Components.Authorization
  3. @using Blazored.LocalStorage
Let's register the below services into the 'Program.cs'.
BlazorWasm_Project/Program.cs:
  1. builder.Services.AddOptions();
  2. builder.Services.AddAuthorizationCore();
  3. builder.Services.AddBlazoredLocalStorage();

Implement AuthenticationStateProvider:

In blazor authentication carried by the 'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'. Let's implement AuthenticationStateProvider to customize the user login.
Let's create a custom AuthenticationStateProvider like 'CustomAuthStateProvider'.
BlazorWasm_Project/Providers/CustomAuthStateProvider:
  1. using System.Security.Claims;
  2. using Microsoft.AspNetCore.Components.Authorization;
  3. namespace Bwasm.Cookie.Providers;
  4. public class CustomAuthStateProvider : AuthenticationStateProvider
  5. private ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity());
  6. public override async Task<AuthenticationState> GetAuthenticationStateAsync()
  7. return new AuthenticationState(claimsPrincipal);
  • (Line: 6) The 'CustomAuthStateProvider' implements the 'Microsoft.AspNetCore.Component.Authorization.AuthenticationStateProvider'.
  • (Line: 8) Initially empty 'ClaimsPrincipal' means no user claims(user not authenticated).
  • (Line: 9-12) Implemented the 'GetAuthenticationStateAsync()' method of type 'Task<AuthenticationState>'. So blazor invokes this method to get information about the authenticated user. If this method returns empty claims then authentication is not taken place and then if this method returns a collection of claims then the user is authenticated with the Blazor application.
Let's add the below namespace into the '_Import.razor'.
BlazorWasm_Project/_Import.razor:
  1. @using Bwasm.Cookie.Providers
Now register the 'CustomAuthStateProvider' in 'Program.cs'.
BlazorWasm_Project/Program.cs:
  1. builder.Services
  2. .AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
In 'App.razor' add the 'CascadingAuthenticationState' and 'AuthorizeRouteView' components to pass the authentication user information to the blazor components.
BlazorWasm_Project/App.razor:
  1. <CascadingAuthenticationState>
  2. <Router AppAssembly="@typeof(App).Assembly">
  3. <Found Context="routeData">
  4. <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
  5. <FocusOnNavigate RouteData="@routeData" Selector="h1" />
  6. </Found>
  7. <NotFound>
  8. <PageTitle>Not found</PageTitle>
  9. <LayoutView Layout="@typeof(MainLayout)">
  10. <p role="alert">Sorry, there's nothing at this address.</p>
  11. </LayoutView>
  12. </NotFound>
  13. </Router>
  14. </CascadingAuthenticationState>

Add Auth Cookie Credential To A Request By Implementing DelegateHandler In Blazor App:

Our Blazor web assembly application and API application run on different domains. So our blazor application invokes the API endpoint then the auth cookie of the API endpoint will not be sent by default. So to send auth cookie along with an API request we have to implement the HttpClient DelegateHandler.
Now let's create our DelegateHandler like 'CookieHandler'.
BlazorWasm_Project/Handlers/CookieHandler.cs
  1. using Microsoft.AspNetCore.Components.WebAssembly.Http;
  2. namespace Bwasm.Cookie.Handler;
  3. public class CookieHandler : DelegatingHandler
  4. protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  5. request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
  6. return await base.SendAsync(request, cancellationToken);
  • (Line: 9) Explicitly defining to pass the browser credentials(auth credentials) for every request if they exist.
Now register our 'CookieHandler' in 'Program.cs'.
BlazorWasm_Project/Program.cs:
  1. builder.Services.AddScoped<CookieHandler>();

Register HttpClient Service In Blazor App:

Let's register the 'Named' HttpClient where we define the API domain and the DelegateHandler.
BlazorWasm_Project/Program.cs:
  1. builder.Services.AddHttpClient("API", options => {
  2. options.BaseAddress = new Uri("https://localhost:7244/");
  3. .AddHttpMessageHandler<CookieHandler>();

Create ApiLogic In Blazor App:

Let's implement all our API logic into a class like 'ApiLogic'. So let's create files like 'IApiLogic.cs', 'ApiLogic.cs'.
BlazorWasm_Project/Logics/IApiLogic.cs:
  1. namespace Bwasm.Cookie.Logics;
  2. public interface IApiLogic
BlazorWasm_Project/Logics/ApiLogic.cs:
  1. namespace Bwasm.Cookie.Logics;
  2. public class ApiLogic: IApiLogic
  3. private readonly IHttpClientFactory _httpClientFactory;
  4. public ApiLogic(IHttpClientFactory httpClientFactory)
  5. _httpClientFactory = httpClientFactory;
  • Here injected the 'HttpClientFactory' instase:
Add the below namespace into the '_Import.razor'.
BlazorWasm_Project/_Import.razor:
@using Bwasm.Cookie.Logics
Register the 'ApiLogic' in Program.cs.
BlazorWasm_Project/Program.cs: 
  1. builder.Services.AddScoped<IApiLogic, ApiLogic>();

Logic To Invoke Use Login API:

Let's create a payload model like 'LoginModel'.
BlazorWasm_Project/Models/LoginModel.cs:
  1. namespace Bwasm.Cookie.Models;
  2. public class LoginModel
  3. public string Email { get; set; }
  4. public string Password { get; set; }
Add the below namespace in '_Import.razor'.
BlazorWasm_Project/_Import.razor:
  1. @using Bwasm.Cookie.Models
Now in 'IApiLogic' add a method definition like the below.
BlazorWasm_Project/Logics/IApiLogic.cs:
  1. Task<string> LoginAsync(LoginModel login);
Now in 'ApiLogic' implement the 'LoginAsync' method.
BlazorWasm_Project/Logics/ApiLogic.cs:
  1. using System.Text;
  2. using System.Text.Json;
  3. using Bwasm.Cookie.Models;
  4. public async Task<string> LoginAsync(LoginModel login)
  5. var client = _httpClientFactory.CreateClient("API");
  6. string payload = JsonSerializer.Serialize(login);
  7. var content = new StringContent(payload, Encoding.UTF8, "application/json");
  8. var response = await client.PostAsync("/Auth/login", content);
  9. if (response.IsSuccessStatusCode)
  10. return "Success";
  11. return "failed";
  • (Line: 8) Creating  HttpClient instance from the IHttpClientFactory using the name of the HttpClient instance registered in 'Program.cs'.
  • (Line: 9&10) Formating user payload.
  • (Line: 11) Invoking the user login endpoint.
  • (Line: 12-19) If authentication is successful return a message like 'Success' or else return 'failed'.

Create Login.razor Component In Blazor App:

Let's create the login form by creating the 'Login.razor'.
BlazorWasm_Project/Pages/Login.razor:(HTML Part)
  1. @page "/login"
  2. @inject IApiLogic _apiLogic
  3. @inject AuthenticationStateProvider _authStateProvider
  4. @inject NavigationManager _navigationManager
  5. @inject ILocalStorageService _localStorageService;
  6. <div class="row">
  7. <div class="col-md-6 offset-md-3">
  8. <legend>User Login</legend>
  9. <div class="mb-3">
  10. <label for="txtEmail" class="form-label">Email</label>
  11. <input @bind="loginModel.Email" type="text" class="form-control" id="txtEmail" />
  12. </div>
  13. <div class="mb-3">
  14. <label for="txtPassword" class="form-label">Password</label>
  15. <input @bind="loginModel.Password" type="password" class="form-control" id="txtPassword" />
  16. </div>
  17. <button type="button" @onclick="UserLogin" class="btn btn-primary">Login</button>
  18. </div>
  19. </div>
  • (Line: 1) Define the route using the '@page' directive.
  • (Line: 2-5) Injected all the required services.
  • (Line: 7-21) Login form.
  • (Line: 12&17) Enable form model binding using the '@bind' directive on textboxes.
  • (Line: 19) Here 'Login' button click event registered with the 'UserLogin' method.
BlazorWasm_Project/Pages/Login.razor:(C# Part)
  1. @code {
  2. private LoginModel loginModel = new LoginModel();
  3. private async Task UserLogin()
  4. var message = await _apiLogic.LoginAsync(loginModel);
  5. if (message == "Success")
  6. await _localStorageService.SetItemAsStringAsync("isauthenticated", "true");
  7. _navigationManager.NavigateTo("/",true);
  • (Line: 2) The 'LoginModel' is used to capture the form data.
  • (Line: 6) Invokes the user login endpoint.
  • (Line: 9) Using LocalStorageService we are setting the 'isauthenticated' value to the browser's local storage. This value helps to invoke the 'UserProfile API' (which will implement in upcoming steps) whose data is used to bind on our Blazor application.
  • (Line: 10) On successful authentication user navigated to the home page by forcibly reloading the page.

Update AuthenticationState With LoggedIn User In Blazor App:

Let's add a new method like 'SetAuthInfo' in 'CustomAuthStateProvider' for updating the 'AuthenticationState'.
Blazorwasm_Project/Providers/CustomAuthStateProvider.cs:
  1. public void SetAuthInfo(UserProfileModel userProfile)
  2. var identity = new ClaimsIdentity(new[]{
  3. new Claim(ClaimTypes.Email, userProfile.Email),
  4. new Claim(ClaimTypes.Name, $"{userProfile.FirstName} {userProfile.LastName}"),
  5. new Claim("UserId", userProfile.ToString())
  6. }, "AuthCookie");
  7. claimsPrincipal = new ClaimsPrincipal(identity);
  8. NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
  • Here 'UserProfileModel' will be the response of the 'user-profile' endpoint(Which we implement in upcoming steps). Creating the new user claims based on the 'UserProfileModel'. Then the newly created ClaimsPrincipal was assigned to the 'claimsPrincipal' variable. The 'NotifyAuthenicationStateChanged' method refreshes the 'AuthenticationState' data.

Logic To Invoke The UserProfile Endpoint In Blazor App:

Let's add a method definition like 'UserProfileAsync' in 'IApiLogic'.
BlazorWasm_Project/Logics/IApiLogic.cs:
  1. Task<(string Message, UserProfileModel? UserProfile)> UserProfileAsync();
Now implement the 'UserProfileAsync' in 'ApiLogic'.
BlazorWasm_Project/Logics/ApiLogic.cs:
  1. public async Task<(string Message, UserProfileModel? UserProfile)> UserProfileAsync()
  2. var client = _httpClientFactory.CreateClient("API");
  3. var response = await client.GetAsync("/Auth/user-profile");
  4. if (response.IsSuccessStatusCode)
  5. return ("Success", await response.Content.ReadFromJsonAsync<UserProfileModel>());
  6. if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
  7. return ("Unauthorized", null);
  8. return ("Failed", null);

Invoke User Profile Endpoint At OnNavigateAsync Method In Blazor App:

Now our goal is to call the user profile endpoint inside of the 'OnNavigateAsync' method. But 'OnNavigateAsync' method gets executed for every navigation. So we have to conditionally invoke the user profile endpoint to reduce unwanted API calls. So we depend on the local storage variable that is 'isuserauthenticated' and 'AuthenticationState' value. So that we can maintain user authentication in our blazor webassembly very comfortably even if the page reloads.
BlazorWasm_Project/App.razor:(Html Part)
  1. @inject ILocalStorageService _localStorageService;
  2. @inject IApiLogic _apiLogic
  3. @inject AuthenticationStateProvider _authStateProvider
  4. <CascadingAuthenticationState>
  5. <Router AppAssembly="@typeof(App).Assembly" OnNavigateAsync="@OnNavigateAsync">
  6. <!-- existing code hidden for display purpose -->
  7. </Router>
  8. </CascadingAuthenticationState>
  • (Line: 1-3) Injected all required services.
  • (Line: 5) The 'OnNavigateAsync' event callback is registered with the 'OnNavigateAsync' method that executes for every navigation.
BlazorWasm_Project/App.razor:(C# Part)
  1. @code{
  2. private async Task OnNavigateAsync(NavigationContext args)
  3. var auth = await _localStorageService.GetItemAsync<string>("isauthenticated");
  4. var user = (await( _authStateProvider as CustomAuthStateProvider)
  5. .GetAuthenticationStateAsync()).User;
  6. if(!string.IsNullOrEmpty(auth) && !user.Identity.IsAuthenticated ){
  7. var response = await _apiLogic.UserProfileAsync();
  8. if (response.Message == "Success")
  9. ( _authStateProvider as CustomAuthStateProvider)
  10. .SetAuthInfo(response.UserProfile);
  11. else if(response.Message == "Unauthorized"){
  12. await _localStorageService.RemoveItemAsync("isauthenticated");
  • (Line: 4) Fetch the browser's local storage value that is 'isauthenticated'.
  • (Line: 5-6) Getting the authenticated user information.
  • (Line: 8-18) If the local storage variable exists and no user information from authenticated state provider which means the page is reloading so at that time we will lose our authentication state provider information so to get that info we will call user profile API then we can re-populate the authentication state provider with user information. So to re-populate the user information we can  call 'CustomAuthStateProvider.SetAuthInfo()' method.

Clear AuthenticationState On User Logout In Blazor App:

Let's implement logic to clear the AuthentictionState for the user logout.
BlazorWasm_Project/Providers/CustomAuthStateProvider.cs:
  1. public void ClearAuthInfo()
  2. claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity());
  3. NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
  • Just assign the empty 'ClaimsPrincipal' object to clear the 'AuthenticationState'.

Logic To Invoke Logout Endpoint In Blazor App:

Let's add a method definition like 'LogoutAsync' in 'IAppLogic'.
BlazorWasm_Project/Logics/IAppLogic.cs:
  1. Task<string> LogoutAsync();
Let's implement 'LogoutAsync' method in 'AppLogic'.
BlazorWasm_Project/Logics/AppLogic.cs:
  1. public async Task<string> LogoutAsync()
  2. var client = _httpClientFactory.CreateClient("API");
  3. var response = await client.PostAsync("/Auth/logout", null);
  4. if (response.IsSuccessStatusCode)
  5. return "Success";
  6. return "Failed";

Create A LoginDisplay.razor Component In Blazor App:

Let's create LoginDisplay.razor component which we will use in the application menu to render the 'Login' links if the user is not authenticated or 'Logout', 'User Name' links if the user is authenticated.
BlazorWasm_Project/Shared/LoginDisplay.razor:(Html Part)
  1. @inject AuthenticationStateProvider _authStateProvider
  2. @inject NavigationManager _navigationManager
  3. @inject IApiLogic _apiLogic
  4. @inject ILocalStorageService _localStorageService;
  5. <AuthorizeView>
  6. <Authorized>
  7. <a href="">@context.User.Identity?.Name!</a>
  8. <button type="button" @onclick="Logout" class="nav-link btn btn-link">Log out</button>
  9. </Authorized>
  10. <NotAuthorized>
  11. <a href="/login">Log in</a>
  12. </NotAuthorized>
  13. </AuthorizeView>
  • (Line: 1-4) Injected the required services.
  • (Line: 8) Rendering the authenticated user name.
  • (Line: 9) Here 'Log out' click event registered with the 'Logout' method.
BlazorWasm_Project/Shared/LoginDisplay.razor:(C# Part)
  1. @code {
  2. private async Task Logout()
  3. var response = await _apiLogic.LogoutAsync();
  4. if (response == "Success")
  5. (_authStateProvider as CustomAuthStateProvider).ClearAuthInfo();
  6. await _localStorageService.RemoveItemAsync("isauthenticated");
  7. _navigationManager.NavigateTo("/", true);
  • (Line: 4) Invoked Logout API.
  • (Line: 7) Clearing the Authenication state provider.
  • (Line: 8) Clearing the local storage variable.
  • (Line: 9) Finally, reload the entire page to clear all user information.
Now render the 'LoginDisplay.razor' in the 'Shared/MainLayout.razor'.
BlazorWasm_Project/Shared/MainLayout.razor:
  1. <LoginDisplay></LoginDisplay>
Step1:
Step 2:
Step 3:
Step 4:

Support Me!
Buy Me A Coffee PayPal Me

Wrapping Up:

Hopefully, I think this article delivered some useful information about Blazor WebAssembly Cookie Authentication. using I love to have your feedback, suggestions, and better techniques in the comment section below.

Follow Me:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK