ASP.NET Core 5 and Blazor Web Assembly Apps: Token Based Authentication in ASP.NET Core 5 APIs and Authenticating it using Blazor WebAssembly Apps

    

Recently, while conducting training in .NET 5 and Blazor, my students have asked me to show the implementation of JSON Web Token based Authentication for ASP.NET Core 5 API and accessing this secure API using Blazor WebAssembly client apps. After the explanation and demo, I thought to write a tutorial on it. I have already posted an article on the ASP.NET Core 3.1 JSON Web Token with concept of JSON Web Token and implementation. You can read the article form this post. For all the new readers, I have provided all steps of implementation Token based authentication in ASP.NET Core 5 API and accessing the API in Blazor WebAssembly application. The figure 1 explains all steps of the implementation



Figure 1: The Blazor WebAssembly Application accessing Token Based Secure API developed using ASP.NET Core 5   

As shown in the figure 1, we will be having Identity database which will store Identity Users, Roles, etc. The application database will store application data e.g. Products Info, etc. The API will have Controllers for managing Identity for the application users e.g. Registering New Users and Authenticating user. The Business App Controller will perform Business operations against Application database. The Blazor WebAssembly client application contains components for User Management and Accessing Business Application Data. The Step-By-Step execution is explained in following points

  1. The Register New User Component is used to make HTTP Request to AuthAPI Controller to register new user.
  2. The AuthAPI controller connects to the Identity database and stores the Users information in AspNetUsers Table if the user is not already exists.
  3. The Login Components sends the users credentials to the AuthAPI controller to Authenticate user.
  4. The AuthAPI controller connects to the Identity database and check if the user exists and the password matches.
  5. Based on the Credentials check, following possible operations will takes place
    1. 5a. If the authentication fails or user does not exist,  then the error response will be send back to the Login Component
    2. 5b. If the Authentication is successful, then the token will be generated.
  6. The JSON Web Token will be send back to the client
  7. The client application has to store the token in its own process (or in sessionStorage, localstorage or in state). The Data Read/Write component will use this token to authenticate requests.
  8. The Data Read/Write component will make call to Business API by sending the token in HTTP Header.
  9. The Business API controller will accept the token and verify the token. 
  10. Based on the Token verifications, following operations will takes place
    1. 10a. If the Token verification failed, then the Unauthorized response will be send to client
    2. 10b. If the Token is verified, then the data read/write operations will take place and response will be send back to the client.
The application is implemented using Visual Studio Enterprise 2022 preview 1. You can use Visual Studio 2019 with .NET 5 SDK. Since the Blazor WebAssembly application provides the .NET Full-Stack development experience, we can share the common entities and common logic in Server API app and Blazor WebAssembly client app by using class library project. The figure 2 provides an idea of the implementation



Figure 2: The Application implementation                     

Step 1: Open Visual Studio and create a new Class Library project of .NET 5. Name this project as SharedModels. In this project add a new class file and name it as Models.cs. In this class file add  classes as shown in Listing 1


using System.ComponentModel.DataAnnotations;

namespace SharedModels.Models
{
    public class LoginUser
    {
        [Required(ErrorMessage = "User Name is Must")]
        public string UserName { get; set; }

        [Required(ErrorMessage = "Password is Must")]
        public string Password { get; set; }
    }

    public class RegisterUser
    {
        [Required(ErrorMessage = "Email is Must")]
        [EmailAddress]
        public string Email { get; set; }
        [Required(ErrorMessage = "Password is Must")]
        [RegularExpression("^((?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])|
(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[^a-zA-Z0-9])|(?=.*?[A-Z])(?=.*?[0-9])(?=.*?[^a-zA-Z0-9])|
(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^a-zA-Z0-9])).{8,}$",
            ErrorMessage = "Passwords must be minimum 8 characters and must be 
string password with uppercase character, number and sepcial character")]
        public string Password { get; set; }
        [Compare("Password")]
        public string ConfirmPassword { get; set; }
    }

    public class ResponseData
    {
        public string Message { get; set; }
    }

 public class Product
    {
        public int ProductId { get; set; }
        public string ProductName { get; set; }
        public int Price { get; set; }
    }
}
Listing 1: Model classes

The code in listing 1 have following classes
  • RegisterUser class will be used to register new user.
  • LoginUser class will be used to authenticate user
  • ResponseData class represents the response format from API to the client application
  • Product class will be used to read products information from API and send it to client

Step 2: In the same solution, add  a new ASP.NET Core API project. Name this project as SecureAPI. Make sure that the target framework selected for the project is .NET 5 and security selected as None. In this project add the reference of the SharedModels project so that we can use entities from it. 

Step 3: In the API project add following packages (You can use Manage NuGet Package window or .NET CLI)
  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Design
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.EntityFrameworkCore.Relational
  • Microsoft.AspNetCore.Identity.EntityFrameworkCore
    • This package will be used to generate ASP.NET Core Identity tables in SQL Server database.
    • This will provide access to following classes
      •  UserManager<IdentityUser>, to create a new User
      • SignInManager<IdentityUser>, to manage user login
  • Microsoft.AspNetCore.Authentication.jwtBearer
  • System.IdentityModel.Tokens.Jwt
If you want to use .NET CLI then to install these packages, open command prompt and navigate to the SecureAPI project folder and run the following command from the command prompt

dotnet add package [PACKAGE-NAME]

Step 4: In the SecureAPI project add new folder and name it as Models. In this folder add a new class file and name it as Models.cs. In this class file add code to hard-code Products data as shown in listing 2


 public class ProductList : List<Product>
    {
        public ProductList()
        {
            Add(new Product { ProductId=1, ProductName ="P1",Price=11000});
            Add(new Product { ProductId = 2, ProductName = "P2", Price = 12000 });
        }
    }
Listing 2: Products List
Note that instead of having Product table in SQL Server database, I have hard-coded the products values. But you can use the data and using EntityFrameworkCore Read/Write operations can be performed.

Step 5: In the Models folder add a new class and name it as JwtSecurityDbContext class. This class will be derived from the IdentityDbContext classWe will use this class to generate the SQL Server database to generate Identity tables to store Users information. Add the code in this class as shown in listing 3


public class JwtSecurityDbContext : IdentityDbContext
    {
        public JwtSecurityDbContext(DbContextOptions<JwtSecurityDbContext> options)
        : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }
    }
Listing 3: DbContext class 

The IdentityDbContext class will be used to generate Database Schema to store Identity information in the SQL Server Database using IdentityUser, IdentityRole, etc. classes.

Step 6 : We need to generate the SQL Server database for storing the Identity information. To connect to the database we need to connect to the SQL Server. Modify the appsettings.json file by adding ConnectionString in it as shown in listing 4


"ConnectionStrings": {
    "JwtConnectionString": "Data Source=.;Initial Catalog=JwtSecurityDb;Integrated Security=SSPI"
  }
Listing 4: The Connection String  

Step 7: Modify the ConfigureServices() method of the Startup class from Startup.cs to register the DbContext class in Dependency Injection Container and also add the code to inform the API the the Identity will be used to access the API from the client application. The code is shown in the listing 5

 services.AddDbContext<JwtSecurityDbContext>(options =>
  {
    options.UseSqlServer(Configuration.GetConnectionString("JwtConnectionString"));
  });
            
  services.AddIdentity<IdentityUser,IdentityRole>(
      options => options.SignIn.RequireConfirmedAccount = false)
       .AddEntityFrameworkStores<JwtSecurityDbContext>();

Listing 5: The Registration of DbContext and adding Identity              

Step 8: Now, lets generate the Identity Database using EntityFrameworkCore Code-First approach. Run the following command from the Command prompt by opening the command prompt and navigating to the SecuteAPI Project folder

Command to generate migrations

dotnet ef migrations add [MigrationName] -c [Namespce.DbContext ClassName]

e.g.

dotnet ef migrations add FirstMigration -c SecureAPI.Models.JwtSecurityDbContext

This will add a new folder in the project of name Migrations. This folder will contain classes for migration

Command to generate database 

dotnet ef database update -c  [Namespce.DbContext ClassName]

e.g.

dotnet ef database update -c  SecureAPI.Models.JwtSecurityDbContext

You can check the database generated once the update command is executed successfully.

Adding Logic for User Registration and Authentication 

Step 9: Modify the appsettings.json by adding keys for JWT Secret and Expiry of the token as shown in Listing 6
"JWTSettings": {
    "SecretKey": "MNyFyANIvTjwFW2StGH73ez1Rf1jGQD0as9+NxE2cor4wwUapS6J3QCqDWQkxwzs8FW8pFpw/0R69aVD8qsWuA==",
    "ExpiryInMinuts": 20
  }


Listing 6: The keys for Token Secret and Expiry 

Please note that, the Secret key is generated using a separate console application using Cryptography. I am providing the sample code for generating the secret in listing 7. You are free to use different mechanism
using System;
using System.Security.Cryptography;

namespace KeyGenerator
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var rNGCryptoServiceProvider = new RNGCryptoServiceProvider())
            {
                var SigningSecretKey = new byte[64];
                rNGCryptoServiceProvider.GetBytes(SigningSecretKey);
                Console.WriteLine($"Secret Key is {Convert.ToBase64String(SigningSecretKey)}");
            }
            Console.ReadLine();
        }
    
    }
}



Listing 7: Code for generating Secret      

Step 10: In the SecureAPI project, add a new folder and name it as Services. In this folder add a new class file and name it as AuthService.cs. This class file will contains logic for registering new user and authenticating user. In this class file add the code as shown in listing 8

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using SharedModels.Models;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Threading.Tasks;

namespace SecureAPI.Services
{
    public class AuthService
    {
        IConfiguration configuration;
        SignInManager<IdentityUser> signInManager;
        UserManager<IdentityUser> userManager;
        //1.
        public AuthService(IConfiguration configuration,
            SignInManager<IdentityUser> signInManager,
            UserManager<IdentityUser> userManager)
        {
            this.configuration = configuration;
            this.signInManager = signInManager;
            this.userManager = userManager;
        }

        //2.
        public async Task<bool> RegisterUserAsync(RegisterUser register)
        {
            bool IsCreated = false;
            var registerUser = new IdentityUser() { 
            	UserName = register.Email,
            	Email = register.Email };
            var result = await userManager.CreateAsync(registerUser, register.Password);
            if (result.Succeeded)
            {
                IsCreated = true;
            }
            return IsCreated;
        }
        
        //3
        public async Task<string> AuthenticateUserAsync(LoginUser inputModel)
        {

            string jwtToken = "";
 
            var result = await signInManager.PasswordSignInAsync(inputModel.UserName,
            inputModel.Password, false, lockoutOnFailure: true);
            if (result.Succeeded)
            {
                
                var secretKey = Convert.FromBase64String(configuration
                		["JWTSettings:SecretKey"]);
                var expiryTimeSpan = Convert.ToInt32(configuration
                		["JWTSettings:ExpiryInMinuts"]);
               

                IdentityUser user = new IdentityUser(inputModel.UserName);

                var securityTokenDescription = new SecurityTokenDescriptor()
                {
                    Issuer = null,
                    Audience = null,
                    Subject = new ClaimsIdentity(new List<Claim> {
                        new Claim("username",user.Id,ToString()),
                    }),
                    Expires = DateTime.UtcNow.AddMinutes(expiryTimeSpan),
                    IssuedAt = DateTime.UtcNow,
                    NotBefore = DateTime.UtcNow,
                    SigningCredentials = new SigningCredentials
                    		(new SymmetricSecurityKey(secretKey),
                    		SecurityAlgorithms.HmacSha256Signature)
                };
                
                var jwtHandler = new JwtSecurityTokenHandler();
                var jwToken = jwtHandler.CreateJwtSecurityToken(securityTokenDescription);
                jwtToken = jwtHandler.WriteToken(jwToken);
            }
            else
            {
                jwtToken = "Login failed";
            }

            return jwtToken;
        }
    }
}


Listing 8: The AuthService to Register new user and Authenticating user
 
The AuthService class the following specifications 
  1. The class is constructor injected with IConfiguration interface and SingInManager<IdentityUser> and UserManager<IdentityUser> classes. This will be used to read configurations from appsettings.json, managing users Sign-In and managing users respectively. 
  2. The RegisterUserAsync() method accepts RegisterUser object as input parameter. This method is used to register new user using UserManager class.
  3. The AuthenticateUserAsync() method accepts the LoginUser object as input parameter. This method is used to sign in the user using SignInManager class. If the user is authenticated successfully, then the JSON Web Token will be generated by reading settings from the appsettings.json file using IConfiuguration object. The SecurityTokenDescriptor class is used to describe token with its Audience, Issuer, Subject, SigningCredentials, etc. properties. The Subject property is used to set the Claims (aka Payload) value that will be stored in token so that the token is validated based on the claim. The SigningCredentials property uses the cryptographic signature and algorithm  which will be used to generate token. This information will also used in token validation process. The JwtSecurityTokenHandler class will use the token description to generate the JSON web token. The AuthenticateUserAsync() method will return the token.
Step 11: In the Controllers folder add a new empty API controller and name it as AuthController. In this controller add the code to access the AuthService methods as shown in listing 9       
using Microsoft.AspNetCore.Mvc;
using SecureAPI.Services;
using SharedModels.Models;
using System.Threading.Tasks;

namespace SecureAPI.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        AuthService authenticationService;
        public AuthController(AuthService authenticationService)
        {
            this.authenticationService = authenticationService;
        }


        [HttpPost]
        public async Task<IActionResult> Register(RegisterUser user)
        {
            if (ModelState.IsValid)
            {
                var IsCreated = await authenticationService.RegisterUserAsync(user);
                if (IsCreated == false)
                {
                    return Conflict("The User Already Present");
                }
                var ResponseData = new ResponseData()
                {
                    Message = $"{user.Email} User Created Successfully"
                };
                return Ok(ResponseData);
            }
            return BadRequest(ModelState);
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginUser inputModel)
        {
            if (ModelState.IsValid)
            {
                var token = await authenticationService.AuthenticateUserAsync(inputModel);
                if (token == null)
                {
                    return Unauthorized("The Authentication Failed");
                }
                var ResponseData = new ResponseData()
                {
                    Message = token
                };

                return Ok(ResponseData);
            }
            return BadRequest(ModelState);
        }
    }
}




Listing 9: The AuthController code

As shown in the listing, the AuthController is constructor injected using AuthService. The controller contains HttpPost methods to register new user and authenticate user.

Step 12: Lets modify the ConfigureServices() method of the Startup class to validate the token and register the AuthService in Dependency Container and adding the CORS service as shown in the listing 10
            services.AddCors(options=> {
                options.AddPolicy("cors", policy=> {
                    policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
                });
            });

            byte[] secretKey = Convert.FromBase64String
            (Configuration["JWTSettings:SecretKey"]);

            
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(secretKey),
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });
            services.AddScoped<AuthService>();

            services.AddControllers().AddJsonOptions(options=> {
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
            });


Listing 10: The Token validation and registration of AuthService and CORS      
To validate the token, TokenValidationParameters class is used. This class contains IssuerSignInKey property. This property reads the secret key from the appsettings.json to check the integrity of the token received from the client.

Step 13: Modify the Configure() method of the Startup class to use Middlewares for CORS and identity as shown listing 11 
 app.UseCors("cors");
 app.UseRouting();
 app.UseAuthentication();
 app.UseAuthorization();


Listing 11: Registering CORS, Authentication and Authorization    

Step 14: In the Controllers folder add a new empty API controller. Name this controller as ProductController. In this  controller add the code as shown in listing 12
[Route("api/[controller]")]
    [Authorize]
    [ApiController]
    public class ProductController : ControllerBase
    {
        ProductList products;
        public ProductController()
        {
            products = new ProductList();
        }
        [HttpGet]
        public IActionResult Get()
        {
            return Ok(products);
        }
    }

Listing 12: The ProductController

Note that the Controller is applied with the [Authorize] attribute to secure the controller. This controllers Get() method the returns List of Products.  

Now since our API Application is ready, we will  build the Blazor WebAssembly client application. 
 
 The Blazor WebAssembly Client Application
Step 15:  In the same solution, add new Blazor WebAssembly application. Name this application as Blazor_Client.  In this application add a NuGet package of name  Blazored.SessionStorage. We need this package so that our Blazor application will be able to use browser's sessionStorage to save token.  In this project, add the SharedModel project reference so that we can use entities defined in this project in Blazor application.

Step 16: In the Pages folder of the project, add a new Razor Component. Name this component as  RegisterUserComponent.razor. We will use this component to register new user. In this component add the code as shown in listing 13
@page "/register"
@using SharedModels.Models
@using System.Text.Json 
@inject HttpClient httpClient
<h3>Register New User Component</h3>
<div class="container">
    <EditForm Model="@user">
         <DataAnnotationsValidator>
            
         </DataAnnotationsValidator>
          <ValidationSummary></ValidationSummary>
        <div class="form-group">
             <label>Email</label>
             <InputText  class="form-control" @bind-Value="@user.Email"></InputText>
        </div>
         <div class="form-group">
             <label>Password</label>
             <InputText type="password" class="form-control" @bind-Value="@user.Password">
             </InputText>
        </div>
         <div class="form-group">
             <label>Confirm Password</label>
             <InputText type="password" class="form-control" 
                    @bind-Value="@user.ConfirmPassword">
             </InputText>
        </div>
         <div class="form-group">
             <button class="btn btn-primary" @onclick="@clear">Clear</button>
             <button class="btn btn-success" @onclick="@register">Register</button>
        </div>
        <hr/> 
        <div class="container">
            <strong>@responseData.Message</strong>
        </div>
    </EditForm>
</div>
@code {
    private RegisterUser user;
    private ResponseData responseData;

    protected override Task OnInitializedAsync()
    {
        user = new RegisterUser();
        responseData = new ResponseData();
        return base.OnInitializedAsync();
    }
    void clear()
    {
        user = new RegisterUser(); 
    }

    async Task register()
    {
        var response = await httpClient.PostAsJsonAsync
        ("http://localhost:60626/api/Auth/Register", user);
        var message = await response.Content.ReadAsStringAsync();
        responseData = JsonSerializer.Deserialize<ResponseData>(message);
    }
}


Listing 13: Register User Component       
The Register User Component is injected with HttpClient class to make call to API.  The EditForm is bound with the RegisterUser object using it user instance. The register() method reads the user object of the type RegisterUser class to post this object to the API to create new user. 

Step 17: In the Pages  folder add a  new Razor Component of name LoginComponent.razor. This component will be used to authenticate the user by calling API. This component  will be injected with HttpClient class to make call to API. This component is also injected with the ISessionStorageService from the Blazored.SessionStorage package.  This component contains EditForm component which is bind with the LoginUser object. The login() method, makes the API call to authenticate the user. If the user is successfully authenticated, the token will be returned by the API and this token will be stored in sessionStorage. The code for the Login Component is shown in listing 14
@page "/login"
@using SharedModels.Models
@using System.Text.Json 
@inject HttpClient httpClient
@inject Blazored.SessionStorage.ISessionStorageService sessionStorage
<h3>Login Component</h3>
<div class="container">
    <EditForm Model="@user">
         <DataAnnotationsValidator>
           
         </DataAnnotationsValidator>
           <ValidationSummary></ValidationSummary>
        <div class="form-group">
             <label>User Name</label>
             <InputText  class="form-control"
             @bind-Value="@user.UserName"></InputText>
        </div>
         <div class="form-group">
             <label>Password</label>
             <InputText type="password" class="form-control"
             @bind-Value="@user.Password"></InputText>
        </div>
        
         <div class="form-group">
             <button class="btn btn-primary" @onclick="@clear">Clear</button>
             <button class="btn btn-success" @onclick="@login">Login</button>
        </div>
        <hr/> 
        <div class="container">
            <strong>@responseData.Message</strong>
        </div>
    </EditForm>
</div>  
@code {
    private LoginUser user;
    private ResponseData responseData;

    protected override Task OnInitializedAsync()
    {
        user = new LoginUser();
        responseData = new ResponseData();
        return base.OnInitializedAsync();
    }
    void clear()
    {
        user = new LoginUser(); 
    }

    async Task login()
    {
        var response = await httpClient
        .PostAsJsonAsync("http://localhost:60626/api/Auth/Login", user);
        var message = await response.Content.ReadAsStringAsync();
        responseData = JsonSerializer
        .Deserialize<ResponseData>(message);
        //save data in session storage
        await sessionStorage
        .SetItemAsStringAsync("token", responseData.Message);
    }
}


Listing 14: The Login Component     

Step 18: In the Pages folder add a new Razor Component, name this component as ProductsComponent.razor. Like the other two components, we have added, this component is injected with HttpClient. To read the token from the sessionStorage, inject the ISessionStorageService in this component. This component will call the API by adding the token in the Http Header, to receive the  products data. The code of the component is shown in listing 15
@page "/products"
@using SharedModels.Models
@inject HttpClient httpClient
@inject Blazored.SessionStorage.ISessionStorageService sessionStorage
<h3>Products List Component</h3>
<div class="container">
     <div class="container">
          <strong>@message</strong>
      </div>
    @if (products.Count == 0 || products == null)
    { 
      <div class="container">
          <strong>No Data is Received</strong>
      </div>
    }
    else
    {
        <table class="table-bordered table-striped">
            <thead>
                <tr>
                    <th>Product Id</th>
                    <th>Product Name</th>
                    <th>Price</th>
                </tr>
            </thead>
            <tbody>
                @foreach(var prd in products)
                {
                    <tr>
                        <td>@prd.ProductId</td>
                        <td>@prd.ProductName</td>
                        <td>@prd.Price</td>
                    </tr>
                }
            </tbody>
        </table>
    }
</div>
@code {
    List<Product> products;
    string message;
    protected override async Task OnInitializedAsync()
    {
        products = new List<Product>();
        // check if the token is available then request the API for Access the data
        string token = await sessionStorage.GetItemAsStringAsync("token");
        if (String.IsNullOrEmpty(token))
        {
            message = "The Authentication is not done, please login";
        }
        else
        {
            httpClient.DefaultRequestHeaders
            .Add("Authorization", $"Bearer {token}");
            products = await httpClient
            .GetFromJsonAsync<List<Product>>("http://localhost:60626/api/Product");  

        } 
    }
}


Listing 15: The ProductComponent   

Step 19: Finally, modify the Main() method of the Program class to register the Session Storage Service in Dependency Container. Add the code in the listing 16 for Session Storage registration
 
.....
builder.Services.AddBlazoredSessionStorage();
....


Listing 16: The registration of session storage 

Step 20: Modify the NavMenu.razorf ile in Shared folder to add navigation links for newly added component as shown in listing 17
 <li class="nav-item px-3">
            <NavLink class="nav-link" href="register">
                <span class="oi oi-list-rich" 
				aria-hidden="true"></span>Register User
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="login">
                <span class="oi oi-list-rich" 
  			aria-hidden="true"></span> Login
            </NavLink>
        </li>
          <li class="nav-item px-3">
            <NavLink class="nav-link"
			href="products">
                <span class="oi oi-list-rich" 
				aria-hidden="true"></span> Get Products
            </NavLink>
        </li>


Listing 17: The Navigation links  
To run the application, make sure that the solution has multiple startup projects, set the SecureAPI project first and Blazor client application as next. Run the application, the API and client project will be loaded 
n the browser. In the Blazor project click on the Register User link and enter the information for creating new user as shown in figure 3


Figure 3: Creating new user   
Click on the Register button, the new user will be registered as shown in figure 4


Figure 4: The New User
Click on the LoginComponent, in this component enter the User Name and Password and click on the Login button, once the Login button is clicked, the token will be received by the client application as shown in figure 5



Figure 5: The Login Success   
Click on the Get Products link to receive the Products List. This will make call to API by reading the token from the sessionStorage. The products will be displayed as shown in figure 6



Figure 6: The Products List 

Without login, if you click on the Get Products, the error will be displayed as shown in figure 7



Figure 7: The error 

The code for the tutorial can be downloaded from this link
Conclusion: The Token Based Authentication is one of the best way to protect API from unauthorized access.  Using Blazor WebAssembly project with Http calls we can implement an End-to-End security in .NET 5 full-stack applications.

Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach