Blazor is a modern web framework from Microsoft used for building interactive web applications using C# and .NET. It's based on a flexible, modular component model well suited for building applications with rich, interactive web user interfaces. Blazor leverages the authentication mechanism of ASP.NET Core to establish the identity of a user.
You've had a glimpse at the features and benefits of Blazor applications and how you can implement them in ASP.NET Core in a previous article. The article you're reading now takes a deep dive into how you can secure Blazor applications, with relevant code examples to demonstrate the concepts and the best practices.
If you're to work with the code examples discussed in this article, you need the following installed in your system:
- Visual Studio 2022
- .NET 9.0
- ASP.NET 9.0 Runtime
If you don't already have Visual Studio 2022 installed on your computer, you can download it from here: https://visualstudio.microsoft.com/downloads/.
At the end of this journey, you'll be able to build high-performance, scalable, and secure Blazor applications in ASP.NET Core 9 and Visual Studio 2022.
Application Security and Its Importance
Securing your applications has become more critical recently because of the surge in security breaches, malware, and other cybersecurity threats. Application security aims to determine and resolve security threats and vulnerabilities in an application before attackers exploit them. Your applications must be secure to avert exposure to confidential information, prevent unauthorized information access through hacking, reduce data breaches, bolster customer trust, and comply with legal requirements and guidelines.
Securing Blazor Applications
When working with Blazor applications, implementing robust security measures is an imperative because of:
- Protecting sensitive data from being compromised
- Thwarting unauthorized access to your application
- Protecting against cross-site scripting (XSS) attacks
You can secure your Blazor applications in several ways:
- Using HTTPS
- Using input validation
- Using anti-forgery tokens
- Leveraging ASP.NET Core security framework
- Using token-based authentication
In the sections that follow, I'll examine how to take advantage of these security strategies to protect your Blazor applications.
Secure a Blazor Server Application
A Blazor Server application is hosted on the server side—its components run on the server inside an ASP.NET Core application. The user interface is rendered on the server and sent to the web browser. It uses a SignalR connection and the WebSockets protocol to perform UI updates and handle events.
Anatomy of a Blazor Server Application
The solution structure of a typical Blazor Server application is comprised of several folders and files. The following is the list of folders in your Blazor Server application:
- Areas: Splits your Blazor Server application into a collection of functional groups. Each of these functional groups contains a set of Razor pages, controllers, views, and models.
- Data: The services and models used to implement the business logic of your application.
- Pages: The visual elements of your Blazor Server application, such as the layout template, main page, and the starting point of the application.
- Shared: The types that are shared across all components of your Blazor Server application, such as
MainLayout.*
,NavMenu.*
, and so on. - wwwroot: The static resources used by your Blazor Server application such as, CSS files and favicon.
Here is the list of the files used in your Blazor Server application and their purpose:
- Program.cs: Contains the startup code of your Blazor Server application
- App.razor: Represents the main Razor component of your Blazor Server application
- _Imports.razor: Contains the necessary code to import the required namespaces used in your Blazor Server application
- appsettings: Contains configuration data used in your Blazor Server application
Create a New Blazor Server Application in .NET 9 and Visual Studio 2022
You can create a project in Visual Studio 2022 in several ways, such as from the Visual Studio 2022 Developer Command Prompt or by launching the Visual Studio 2022 IDE. When you launch Visual Studio 2022, you'll see the Start window. You can choose Continue without code to launch the main screen of the Visual Studio 2022 IDE.
Now that you know the basics, let's start setting up the project. To create a new ASP.NET Core 8 Project in Visual Studio 2022:
- Start the Visual Studio 2022 IDE.
- In the Create a new project window, select Blazor Server App and click Next to move on.
- Specify the project name as Blazor_Server_Demo and the path where it should be created in the Configure your new project window.
- In the next screen, specify the target framework and authentication type as well. Ensure that the Configure for HTTPS, and Do not use top-level statements checkboxes are unchecked because you won't use any of these in this example.
- If you want the solution file and project to be created in the same directory, you can optionally check the Place solution and project in the same directory checkbox. Click Next to move on.
- Click Create to complete the process.
A new Blazor Server application project is created. Figure 1 shows the default solution structure of the Blazor Server App you just created.

You can implement authentication in your Blazor application using the built-in authentication provided by Microsoft for Blazor applications. To use the built-in authentication mechanism, follow the same sequence of steps outlined in the preceding section to create a Blazor Server application. However, you should select the Authentication type you'd like to use in the application. In this example, select the Authentication type as Individual Accounts in the Additional information window, as shown in Figure 2.

Finally, click Create to complete the process. A new Blazor Server application is created with authentication enabled. When you execute the application, you'll see the Register and Login buttons in the header, as shown in Figure 3.

As shown in Figure 3, there are three hyperlink buttons displayed at the top of the webpage. Because no application user has been created yet, you can click on the Register button to register a new user in the application. Now, provide the necessary authentication details for the new user referring to Figure 4.

Because no user has been registered yet, you'll be presented with the screen in Figure 5 prompting you to execute migrations.

Next, click on the Apply Migrations button to run migrations and set up the database. Once the migrations have been executed, you'll be presented with the screen shown in Figure 6.

Once you refresh the screen, you'll be presented with a screen that prompts you to confirm your account, as shown in Figure 7.

Once you've confirmed your account, you'll be presented with the screen shown in Figure 8 that says registration is confirmed for the user.

Finally, you can click on the Login button to invoke the Login screen where you can enter the user's credentials to log in and access the application, as shown in Figure 9. You can then use the Login screen to enter user credentials to log in to the application.

If the entered credentials are valid, you'll see the screen shown in Figure 10 showing that the user has been authenticated and logged in to the application.

If you already have a Blazor application with you, you can't follow this approach to implement authentication in your Blazor applications. It's preferable to implement an out-of-the-box authentication to overcome the limitations of this approach. In the next section, you'll examine how you can take advantage of custom authentication to secure the application.
The AuthorizeView Component
An AuthorizeView
component named LoginDisplay.razor
will also be created in the Shared
folder having the following code:
<AuthorizeView>
<Authorized>
<a href="Identity/Account/Manage">
Hello, @context.User.Identity?.Name!
</a>
<form method="post" action="Identity/Account/Logout">
<button type="submit" class="nav-link btn btn-link">
Log out
</button>
</form>
</Authorized>
<NotAuthorized>
<a href="Identity/Account/Register">Register</a>
<a href="Identity/Account/Login">Log in</a>
</NotAuthorized>
</AuthorizeView>
The AuthorizeView
component is comprised of two child components, AuthorizeView
and NotAuthorized
. The former is used to illustrate that the user is logged in, i.e., the user has been authorized successfully, and the latter is used to show that the user is logged out, i.e., the user is not authorized to access the application.
The Blazor Server application also generates a class named RevalidatingIdentityAuthenticationStateProvider
for you. This class extends the RevalidatingServerAuthenticationStateProvider
that inherits the ServerAuthenticationStateProvider
class, which, in turn, extends the AuthenticationStateProvider
abstract class.
Secure a Blazor WebAssembly Application
Blazor WebAssembly (WASM is an acronym for WebAssembly) is a framework for building single-page applications (SPAs) that enables you to execute C# code from client-side applications. It allows you to represent application functionality using C# instead of JavaScript. Blazor WebAssembly applications are comprised of a set of Razor components built using standard C# classes.
In a typical ASP.NET Core hosted model, you'll observe three projects:
- [ProjectName].Server
- [ProjectName].Client
- [ProjectName].Shared
Although the server project hosts the client project, the client project runs directly in the context of a web browser at the client side. When the first request arrives, the compiled application, together with all its dependencies and the .NET Core runtime, are downloaded at the client side.
When you create a standalone Blazor WebAssembly application in .NET 7, you'll observe three projects created automatically for you. These are the Server/API project, the Client project, and a Shared project. You can also create these projects in .NET 9 manually as a workaround. In this example, you'll create the following three projects in the solution:
- BillingSystem.Server
- BilligSystem.Client
- Billing.Shared
The server project is where you usually write the back-end logic (API, services, etc.). The shared project typically contains libraries that are used across both the server and client projects. The client project is where you build your user interface components.
In the next section, I'll examine how you can take advantage of custom authentication strategies to implement security in a real-life Blazor Web Assembly application.
Anatomy of a Blazor WebAssembly Application
A typical Blazor WebAssembly project is comprised of the following folders:
- wwwroot: Contains static resources used in your Blazor WebAssembly application
- Shared: Contains a collection of shared components used in your Blazor WebAssembly application
- Pages: The visual elements of your Blazor WebAssembly application
The following is the list of files in a Blazor WebAssembly application:
- App.razor: The razor component of your Blazor WebAssembly application
- _Imports.razor: A file used to specify the required namespaces used in your Blazor WebAssembly application
- Program.cs: The file that contains the start-up code of your Blazor WebAssembly application
The following section examines how to build a microservices-based real-life application using Blazor WebAssembly. This application leverages EF Core to talk to the underlying database for data storage and retrieval.
Implementing a Microservices-Based Billing System
Microservices architecture encompasses a conglomeration of loosely coupled components that can be built using a collection of homogenous or heterogenous technologies. In a typical microservices-based application, there is a single application comprised of a suite of small, independently deployable services, each of which executes and communicates with the others via lightweight communication mechanisms. There's a plethora of benefits of microservices architecture, such as enhanced scalability, improved ROI, faster releases, and reduced coupling.
In this application, you'll build a secure microservices-based Blazor WebAssembly application. To implement security, you can leverage the ASP.NET Core Identity Framework, as shown in Figure 11.

Alternatively, you can implement custom authentication and store the security related database tables either in a separate database or in the application database.
You can create a project in Visual Studio 2022 in several ways, such as, from the Visual Studio 2022 Developer Command Prompt or by launching the Visual Studio 2022 IDE. When you launch Visual Studio 2022, you'll see the Start window. You can choose Continue without code to launch the main screen of the Visual Studio 2022 IDE.
Now that you know the basics, let's start setting up the project. To create a new ASP.NET Core 8 Project in Visual Studio 2022:
- Start the Visual Studio 2022 IDE.
- In the Create a new project window, select Blazor Web App and click Next to move on.
- Specify the project name as BillingSystem and the path where it should be created in the Configure your new project window.
- If you want the solution file and project to be created in the same directory, you can optionally check the Place solution and project in the same directory checkbox. Click Next to move on.
- In the next screen, specify the target framework and authentication type as well. Ensure that the Configure for HTTPS and Do not use top-level statements checkboxes are unchecked because you won't use any of these in this example.
- Next, specify the Interactive render mode and Interactivity location.
- Ensure that the Include sample pages checkbox is checked if you would like to have sample pages added to your project
- Click Create to complete the process.
A new Blazor Web App project is created. Figure 12 shows the default solution structure.

In this section, you'll build a simple Billing System based off microservices architecture. The types (classes, structs, records, and interfaces) to be used in this application are:
Models
Invoice
Product
Customer
Shipment
Data
ShipmentDbContext
IShipmentRepository
ShipmentRepository
APIs
ShipmentController
Create the Logical Model
The following entities make up the logical blueprint for a typical billing system:
- Invoice: Can record and maintain all information about each invoice, such as customer ID, invoice date, and payment due date for an invoice.
- Invoice detail: Captures and keeps billing information about each transaction for billing purposes.
- Product: Captures the goods or services available in any business. The data captured includes product ID, product name, description, price, etc.
- Customer: Captures and keeps data on a customer, such as the customer ID, customer name, and other contact details for the customer.
Listing 1 shows the database script for creating the database tables for the billing system.
Listing 1: The database script for our billing system database
-- Product table
CREATE TABLE Product (
Product_Id UniqueIdentifier PRIMARY KEY,
Product_Name VARCHAR(255) NOT NULL,
Product_Description TEXT,
Product_Price DECIMAL(10, 2) NOT NULL
);
-- Customer table
CREATE TABLE Customer (
Customer_Id UniqueIdentifier PRIMARY KEY,
Customer_Name VARCHAR(255) NOT NULL,
Customer_Address VARCHAR(255) NOT NULL,
Customer_Contact VARCHAR(255),
Customer_Phone VARCHAR(20),
Customer_Email VARCHAR(20)
);
-- Invoice table
CREATE TABLE Invoice (
Invoice_Id UniqueIdentifier PRIMARY KEY,
Customer_Id UniqueIdentifier NOT NULL,
Invoice_Amount DECIMAL(10, 2),
Invoice_Date DATE NOT NULL,
Payment_Due_Date DATE NOT NULL,
FOREIGN KEY (Customer_Id) REFERENCES Customer(Customer_Id)
);
-- Invoice_Detail table
CREATE TABLE Invoice_Detail (
Invoice_Details_Id UniqueIdentifier PRIMARY KEY,
Invoice_Id UniqueIdentifier NOT NULL,
Product_Id UniqueIdentifier NOT NULL,
Discount DECIMAL(10, 2),
FOREIGN KEY (Invoice_Id) REFERENCES Invoice(Invoice_Id),
FOREIGN KEY (Product_Id) REFERENCES Product(Product_Id)
);
-- Shipment table
CREATE TABLE Shipment (
Shipment_Id UniqueIdentifier PRIMARY KEY,
Invoice_Id UniqueIdentifier NOT NULL,
Shipment_Address VARCHAR(255) NOT NULL,
Shipment_Contact VARCHAR(255),
Shipment_Phone VARCHAR(20),
Shipment_Date DATE NOT NULL,
Arrival_Date DATE NOT NULL,
FOREIGN KEY (Invoice_Id) REFERENCES Invoice(Invoice_Id)
);
Figure 13 demonstrates the database diagram of the Billing System database.

The Types Pertaining to the Shipment Microservice
In this example, you'll build the Shipment microservice application or the Shipment API. The shipment microservice application is comprised of the following files:
- Shipment.cs: Contains details pertaining to shipment such as invoice ID, contact person, phone number, address, etc.
- IShipmentRepository.cs: Represents the
IProductRepository
interface that contains the declaration of the operations supported by the product repository. - ProductRepository.cs: Implements the members of the
IProductRepository
interface. - ProductDbContext.cs: The product data context used to perform CRUD operations for the Product table in the database.
- appsettings.json: Where you can configure the database connection string, logging metadata, etc.
- Program.cs: Any ASP.NET Core application contains a
Program.cs
file where the startup code required by the application resides and is configured. You can specify dependency injection (DI), configuration, middleware, and other information in this file.
Specify the Database Connection String
In ASP.NET Core, the application's settings are stored in a file known as appsettings.json.
This file is created by default when you create a new ASP.NET Core project. You can take advantage of the ConnectionString
property to retrieve or store the connection string for a database. You can specify the connection string in the appsettings.json
file, as shown below:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"SCSDbSettings": "Write your connection string here."
},
"AllowedHosts": "*"
}
You'll use this connection string to enable the application to connect to the database in a section later in this article.
An application requires a connection string to establish a connection to the database. The connection string contains the database name to which to connect, the instance name of the database server where the database resides, the credentials of the database server, etc.
Install Entity Framework Core
So far, so good. The next step is to install the necessary NuGet Package(s) for working with Entity Framework Core and SQL Server. To install these packages into your project, right-click on the solution and then select Manage NuGet Packages for Solution….
Once the window pops up, search for the NuGet packages to add to your project. To do this, type in the packages named Microsoft.EntityFrameworkCore
, Microsoft.EntityFrameworkCore.Design
, Microsoft.EntityFrameworkCore.Tools
, and Microsoft.EntityFrameworkCore.SqlServer
in the search box of the NuGet Package Manager screen and install them one after the other.
Alternatively, you can type the commands shown below at the NuGet Package Manager Command Prompt:
PM> Install-Package Microsoft.EntityFrameworkCore
PM> Install-Package Microsoft.EntityFrameworkCore.Design
PM> Install-Package Microsoft.EntityFrameworkCore.Tools
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
You can also install these packages by executing the following commands at the Windows Shell:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Create the Model Classes
First off, create two solution folders named Models and DataAccess. The former will contain one or more model classes and the latter will have the data context and repository interfaces and classes. It should be noted that you can always create multiple data context classes in the same project. If your data context class contains many entity references, it's a good practice to split the data context among multiple data context classes rather than have one large data context class.
Create a new class called Shipment in a file named Shipment.cs
inside the Models
folder, and write the following code in there:
public record Shipment
{
public Guid Shipment_Id { get; set; }
public Guid Invoice_Id { get; set; }
public string Shipment_Contact { get; set; } = default!;
public string Shipment_Address { get; set; } = default!;
public string Shipment_Phone { get; set; } = default!;
public DateTime Shipment_Date { get; set; } = default!;
public DateTime Arrival_Date { get; set; } = default!;
}
EF Core enables splitting a data context into multiple smaller data contexts, each representing a particular domain or module. However, certain downsides exist, such as the inability to join across contexts and, in some cases, more complicated transaction handling.
Let's now create the data context class to enable the application to interact with the database to perform CRUD (Create, Read, Update, and Delete) operations. To do this, create a new class named ShipmentDbContext
that extends the DbContext
class of EF Core, and write the following code in there:
public class ShipmentDbContext : DbContext
{
public DbSet<Models.Shipment> Shipments { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
}
In the preceding snippet, the statement base.OnConfiguring(optionsBuilder)
calls the OnConfiguring
method of the base class of your ShipmentDbContext
. Because the base class of the ShipmentDbContext
class is DbContext, the call does nothing in particular.
In Entity Framework Core (EF Core), a data context is a component by which an application connects to a database and performs CRUD.
You can specify your database connection string in the OnConfiguring
overloaded method of the ShipmentDbContext
class. However, in this implementation, you'll store the database connection settings in the AppSettings.json
file and read it in the Program.cs
file to establish a database connection.
Note that your custom data context class (the ShipmentDbContext
class in this example) must expose a public constructor that accepts an instance of type DbContextOptions<ApplicationDbContext>
as an argument. This is needed to enable the runtime to pass the context configuration using a call to the AddDbContext()
method to your custom DbContext class. The following code snippet illustrates how you can define a public constructor for your data context class:
public ShipmentDbContext(DbContextOptions<ShipmentDbContext> options,
IConfiguration configuration) : base(options)
{
_configuration = configuration;
}
Seed the Database
You might often want to work with data seeding when using Entity Framework Core (EF Core) to populate a blank database with an initial or minimal data set. Data seeding is a one-time process of loading data into a database. The EF Core framework provides an easy way to seed the data using the OnModelCreating()
method of the DbContext
class.
To generate fake data in your ASP.NET Core application, you can take advantage of the Bogus open-source library. It helps to seed your database by taking advantage of randomly generated but realistic data. To use this library, you should install the Bogus library from NuGet into your project.
In computing, fake data implies data that is artificially generated and looks similar to real-world data. Usually, such data is used for testing software applications.
The following code snippet illustrates how you can generate data using random data using Bogus library:
private Shipment[] GenerateShipmentData()
{
var invoice_Id = Guid.NewGuid();
var data = new Faker<Shipment>()
.RuleFor(x => x.Shipment_Id, f => Guid.NewGuid())
.RuleFor(x => x.Invoice_Id, invoice_Id)
.RuleFor(x => x.Shipment_Contact, f => f.Person.FullName)
.RuleFor(x => x.Shipment_Phone, f => f.Person.Phone)
.RuleFor(x => x.Shipment_Date, f => DateTime.UtcNow);
return data.Generate(count: 5).ToArray();
}
Invoke the GenerateShipmentData
method in the OnModelCreating
method to populate the database with randomly generated data as shown in the following piece of code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Shipment>().ToTable("Shipment");
modelBuilder.Entity<Shipment>().HasKey(p => p.Shipment_Id);
var shipmentData = GenerateShipmentData();
modelBuilder.Entity<Shipment>().HasData(shipmentData);
}
The complete source code of the ShipmentDbContext
class is given in Listing 2.
Listing 2: The ShipmentDbContext class
public class ShipmentDbContext : DbContext
{
private readonly IConfiguration _configuration;
public ShipmentDbContext(DbContextOptions<ShipmentDbContext> options,
IConfiguration configuration) : base(options)
{
_configuration = configuration;
}
protected override void OnConfiguring(DbContextOptionsBuilder
optionsBuilder)
{
_ = optionsBuilder.UseSqlServer(_configuration.GetConnectionString(
"DefaultConnection")).EnableSensitiveDataLogging();
}
public DbSet<Shipment> Shipments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Shipment>().ToTable("Shipment");
modelBuilder.Entity<Shipment>().HasKey(p => p.Shipment_Id);
var shipmentData = GenerateShipmentData();
modelBuilder.Entity<Shipment>().HasData(shipmentData);
}
private Shipment[] GenerateShipmentData()
{
var invoice_Id = Guid.NewGuid();
var data = new Faker<Shipment>()
.RuleFor(x => x.Shipment_Id, f => Guid.NewGuid())
.RuleFor(x => x.Invoice_Id, invoice_Id)
.RuleFor(x => x.Shipment_Contact, f => f.Person.FullName)
.RuleFor(x => x.Shipment_Phone, f => f.Person.Phone)
.RuleFor(x => x.Shipment_Date, f => DateTime.UtcNow);
return data.Generate(count: 5).ToArray();
}
}
Create the IShipmentRepository Interface
Create an interface named IShipmentRepository
in a file having the name as IShipmentRepository.cs
and write the following code in there:
public interface IShipmentRepository
{
public Task<List<Shipment>> GetShipmentsAsync();
public Task<Shipment> GetShipmentAsync(Guid shipmentId);
public Task CreateShipmentAsync(Shipment shipment);
public Task UpdateShipmentAsync(Shipment shipment);
public Task DeleteShipmentAsync(Guid shipmentId);
}
Create the ShipmentRepository Class
Now, create a new class named ShipmentRepository
that implements the IShipmentRepository
interface in a file having the same name with a .cs
extension. Next, write the following code in there:
public class ShipmentRepository : IShipmentRepository
{
}
Listing 3 shows how to implement the methods of the IShipmentRepository
interface.
Listing 3: Implemented methods of the IShipmentRepository interface
public async Task<List<Shipment>> GetShipmentsAsync()
{
return await _shipmentDbContext.Shipments.ToListAsync();
}
public async Task<Shipment> GetShipmentAsync(Guid Id)
{
return await _shipmentDbContext.Shipments
.FirstOrDefaultAsync(x => x.Shipment_Id == Id);
}
public async Task CreateShipmentAsync(Shipment shipment)
{
await _shipmentDbContext.Shipments.AddAsync(shipment);
}
public async Task UpdateShipmentAsync(Shipment shipment)
{
await _shipmentDbContext.Shipments
.Where(s => s.Shipment_Id == shipment.Shipment_Id)
.ExecuteUpdateAsync(s => s
.SetProperty(x => x.Shipment_Id, shipment.Shipment_Id)
.SetProperty(x => x.Invoice_Id, shipment.Invoice_Id)
.SetProperty(x => x.Shipment_Contact, shipment.Shipment_Contact)
.SetProperty(x => x.Shipment_Phone, shipment.Shipment_Phone)
.SetProperty(x => x.Shipment_Date, shipment.Shipment_Date)
.SetProperty(x => x.Arrival_Date, shipment.Arrival_Date)
.SetProperty(x => x.Shipment_Address, shipment.Shipment_Address)
.SetProperty(x => x.Shipment_Id, shipment.Shipment_Id));
}
public async Task DeleteShipmentAsync(Guid shipmentId)
{
await _shipmentDbContext.Shipments
.Where(s => s.Shipment_Id == shipmentId)
.ExecuteDeleteAsync();
}
The complete source code of the ShipmentRepository
class is given in Listing 4.
Listing 4: The ShipmentRepository class
public class ShipmentRepository : IShipmentRepository
{
private readonly ShipmentDbContext _shipmentDbContext;
public ShipmentRepository(ShipmentDbContext shipmentDbContext)
{
_shipmentDbContext = shipmentDbContext;
_shipmentDbContext.Database.EnsureCreated();
}
public async Task<List<Shipment>> GetShipmentsAsync()
{
return await _shipmentDbContext.Shipments.ToListAsync();
}
public async Task<Shipment> GetShipmentAsync(Guid Id)
{
return await _shipmentDbContext.Shipments
.FirstOrDefaultAsync(x => x.Shipment_Id == Id);
}
public async Task CreateShipmentAsync(Shipment shipment)
{
await _shipmentDbContext.Shipments.AddAsync(shipment);
}
public async Task UpdateShipmentAsync(Shipment shipment)
{
await _shipmentDbContext.Shipments.Where(s => s.Shipment_Id ==
shipment.Shipment_Id).ExecuteUpdateAsync(s =>
s.SetProperty(x => x.Shipment_Id, shipment.Shipment_Id)
.SetProperty(x => x.Invoice_Id, shipment.Invoice_Id)
.SetProperty(x => x.Shipment_Contact, shipment.Shipment_Contact)
.SetProperty(x => x.Shipment_Phone, shipment.Shipment_Phone)
.SetProperty(x => x.Shipment_Date, shipment.Shipment_Date)
.SetProperty(x => x.Arrival_Date, shipment.Arrival_Date)
.SetProperty(x => x.Shipment_Address, shipment.Shipment_Address)
.SetProperty(x => x.Shipment_Id, shipment.Shipment_Id));
}
public async Task DeleteShipmentAsync(Guid shipmentId)
{
await _shipmentDbContext.Shipments.Where(s => s.Shipment_Id ==
shipmentId).ExecuteDeleteAsync();
}
}
Register the ShipmentRepository Instance
The following code snippet illustrates how an instance of type IShipmentRepository
is added as a scoped service to the IServiceCollection
in the Program.cs
file.
builder.Services.AddScoped<IShipmentRepository, ShipmentRepository>();
Now, create a new controller named ShipmentController
in the Controllers
folder of the project. The following code snippet shows how you can take advantage of constructor injection to pass an instance of type IShipmentRepository
and then use it to store and retrieve shipment data to and from the database.
[Route("api/[controller]")]
[ApiController]
public class ShipmentController : ControllerBase
{
private IShipmentRepository _shipmentRepository;
public ShipmentController(IShipmentRepository supplierRepository)
{
_shipmentRepository = supplierRepository;
}
}
The following code snippet shows the action methods for creating, updating, and deleting shipment data.
[HttpGet("GetShipment")]
public async Task<IActionResult> GetShipment(Guid shipmentId)
{
return Ok(await _shipmentRepository.GetShipmentAsync(shipmentId));
}
[HttpGet("GetShipments")]
public async Task<IActionResult> GetShipments()
{
return Ok(await _shipmentRepository.GetShipmentsAsync());
}
[HttpPost("CreateShipment")]
public async Task<IActionResult> CreateOrder([FromBody] Shipment shipment)
{
await _shipmentRepository.CreateShipmentAsync(shipment);
return Ok(shipment.Shipment_Id);
}
[HttpPost("UpdateShipment")]
public async Task<IActionResult> UpdateOrder([FromBody] Shipment shipment)
{
await _shipmentRepository.UpdateShipmentAsync(shipment);
return NoContent();
}
[HttpDelete("DeleteShipment{id}")]
public async Task<IActionResult> DeleteShipment(Guid shipmentId)
{
await _shipmentRepository.DeleteShipmentAsync(shipmentId);
return NoContent();
}
The complete source code of the ShipmentController
class is given in Listing 5.
Listing 5: The ShipmentController class
[Route("api/[controller]")]
[ApiController]
public class ShipmentController : ControllerBase
{
private IShipmentRepository _shipmentRepository;
public ShipmentController(IShipmentRepository supplierRepository)
{
_shipmentRepository = supplierRepository;
}
[HttpGet("GetShipment")]
public async Task<IActionResult> GetShipment(Guid shipmentId)
{
return Ok(await _shipmentRepository.GetShipmentAsync(shipmentId));
}
[HttpGet("GetShipments")]
public async Task<IActionResult> GetShipments()
{
return Ok(await _shipmentRepository.GetShipmentsAsync());
}
[HttpPost("CreateShipment")]
public async Task<IActionResult> CreateOrder([FromBody] Shipment shipment)
{
await _shipmentRepository.CreateShipmentAsync(shipment);
return Ok(shipment.Shipment_Id);
}
[HttpPost("UpdateShipment")]
public async Task<IActionResult> UpdateOrder([FromBody] Shipment shipment)
{
await _shipmentRepository.UpdateShipmentAsync(shipment);
return NoContent();
}
[HttpDelete("DeleteShipment{id}")]
public async Task<IActionResult> DeleteShipment(Guid shipmentId)
{
await _shipmentRepository.DeleteShipmentAsync(shipmentId);
return NoContent();
}
}
Execute the Billing System Application
Finally, press F5 while you're inside the Visual Studio 2022 IDE to run the application. Figure 14 shows how the home page of the Billing System application looks in your web browser.

In the next section, I'll examine how you can secure the Billing System application so that only authorized users can access it.
Secure the Billing System Application
In this section, I'll examine how you can secure the Billing System application to thwart any unauthorized requests to access the application. You'll use the following types (classes, structs, records, and interfaces) to implement the security module of the BillingSystem application.
Models
ApplicationUser
Data
ApplicationDbContext
IShipmentRepository
ShipmentRepository
APIs
AuthenticationController
Create the Models
Create a new solution folder named Models inside the BillingSystem project you created earlier. Create a model class named ApplicationUser
that extends the IdentityUser
class of the ASP.NET Core Identity framework and write the following piece of code in there:
using Microsoft.AspNet.Identity.EntityFramework;
namespace BillingSystem.Models
{
public class ApplicationUser : IdentityUser
{
}
}
Next, create a class library project called BillingSystem.Shared. Inside this project, create a solution folder named Models
and create the LoginRequest.cs
with the following code in it:
using System.ComponentModel.DataAnnotations;
namespace BillingSystem.Shared.Models
{
public class LoginRequest
{
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
}
}
Create a new class named RegisterRequest
in a file named RegisterRequest.cs
and write the following code in there:
using System.ComponentModel.DataAnnotations;
namespace BillingSystem.Shared.Models
{
public class RegisterRequest
{
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
[Required]
[Compare(nameof(Password), ErrorMessage = "Passwords do not match!")]
public string ConfirmPassword { get; set; }
}
}
Create the User Interface
Create a new file called Login.razor
that generates the user interface for enabling a user to log in to the application. Listing 6 shows the complete source code of the Login.razor
file.
Listing 6: The Login.razor file
@page "/login"
@using BillingSystem.Shared.Models
<h3>Login</h3>
<EditForm Model="loginRequest" OnValidSubmit="OnLoginUser">
<DataAnnotationsValidator />
<div class="form-group">
<label for="username">Username:</label>
<InputText id="username" class="form-control"
@bind-Value="@loginRequest.Username" />
</div>
<div class="form-group">
<label for="password">Password:</label>
<InputText id="password" type="password" class="form-control"
@bind-Value="@loginRequest.Password" />
</div>
<button type="submit" class="btn btn-primary">Login</button>
<label class="text-danger">@error</label>
<NavLink href="register">
<h6 class="font-weight-normal text-center">
Create new user
</h6>
</NavLink>
</EditForm>
@code {
LoginRequest loginRequest { get; set; } = new LoginRequest();
async Task OnLoginUser()
{
try
{
//Code to authenticate the user
}
catch (Exception ex)
{
//Code to handle exceptions
}
}
}
Create another new file named Register.razor
to register a new user. Listing 7 shows the complete source code of the Register.razor
file.
Listing 7: The Register.razor file
@page "/register"
@using BillingSystem.Shared.Models
<h3>Register</h3>
<EditForm Model="@registerModel" OnValidSubmit="@OnRegisterUser">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="username">Username:</label>
<InputText id="username" class="form-control"
@bind-Value="@registerRequest.Username" />
</div>
<div class="form-group">
<label for="password">Password:</label>
<InputText id="password" type="password" class="form-control"
@bind-Value="@registerRequest.Password" />
</div>
<div class="form-group">
<label for="confirmpassword">Confirm Password:</label>
<InputText id="confirmpassword" type="password" class="form-control"
@bind-Value="registerRequest.ConfirmPassword" />
</div>
<button type="submit" class="btn btn-success">Register</button>
</EditForm>
@code
{
RegisterRequest registerRequest { get; set; } = new RegisterRequest();
async Task OnRegisterUser()
{
try
{
//Code to register a new user
}
catch (Exception ex)
{
//Code to handle exceptions
}
}
}
An Introduction to the ASP.NET Core Identity Framework
Identity Server is an open-source framework for implementing identity and access control in.NET applications. It implements OpenID Connect (OIDC) and OAuth 2.0 standards and integrates with the ASP.NET Core Identity framework and provides a common way to authenticate requests in ASP.NET Core applications. ASP.NET Core Identity is a built-in membership system used to implement log-in functionality in an application. It provides several features such as user authentication, roles- and claims-based authorization, integration with external providers such as Facebook, Google, etc.
You can use the ASP.NET Core Identity Framework for creating, updating, deleting, and querying user accounts. You can use this framework to implement register, log in, and log out functionality in your ASP.NET Core application seamlessly. Blazor leverages ASP.NET Core's authentication mechanism to establish a user's identity. However, contrary to the Blazor communication model, the ASP.NET Core Identity Framework is designed to use the HTTP request-response pattern.
When building web apps that use the ASP.NET Core Identity Framework, user management interfaces in the application should use Razor Pages instead of Razor components. Although it's possible, Microsoft does not endorse or provide support for Razor components handling identity operations.
Create the Application Data Context
You'll now create a data context named ApplicationDbContext
inside a file named ApplicationDbContext.cs
and write the following code in there:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDBContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
You should now define the connection string to be used to connect to the database and add EF Core capabilities to your application. To do this, write the following code in the Program.cs
file:
services.AddDbContext<ApplicationDBContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
Configure the Identity Framework for Your Blazor WebAssembly Application
Add the following statements in the Program.cs
file of the server project to set up identity framework.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDBContext>();
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
Using a Single Data Context
While working with EF Core, you can create one or more data contexts for your application. In this example, I've created two data contexts:
- ApplicationDbContext
- ShipmentDbContext
You can also merge the source code of all your data context classes into the ApplicationDbContext
class so that you have only one data context in the application. To do this, overwrite the source code of the ApplicationDbContext
class and merge the source code of the ShipmentDbContext
class inside it. The complete source code of the merged ApplicationDbContext
class is given in Listing 8.
Listing 8: The merged ApplicationDbContext class
using BillingSystem.Server.Models;
using BillingSystem.Shared.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Bogus;
namespace BillingSystem.Server.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private readonly IConfiguration _configuration;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options) : base(options)
{
}
public DbSet<Shipment> Shipments { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder
optionsBuilder)
{
_ = optionsBuilder.UseSqlServer(_configuration.GetConnectionString(
"DefaultConnection")).EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Ignore<Microsoft.AspNetCore.Identity
.IdentityUserLogin<string>>();
modelBuilder.Ignore<Microsoft.AspNetCore.Identity
.IdentityUserRole<string>>();
modelBuilder.Ignore<Microsoft.AspNetCore.Identity
.IdentityUserClaim<string>>();
modelBuilder.Ignore<IdentityUserToken<string>>();
modelBuilder.Ignore<IdentityUser<string>>();
modelBuilder.Ignore<ApplicationUser>();
modelBuilder.Entity<Shipment>().ToTable("Shipment");
modelBuilder.Entity<Shipment>().HasKey(p => p.Shipment_Id);
var shipmentData = GenerateShipmentData();
modelBuilder.Entity<Shipment>().HasData(shipmentData);
}
private Shipment[] GenerateShipmentData()
{
var invoice_Id = Guid.NewGuid();
var data = new Faker<Shipment>()
.RuleFor(x => x.Shipment_Id, f => Guid.NewGuid())
.RuleFor(x => x.Invoice_Id, invoice_Id)
.RuleFor(x => x.Shipment_Contact, f => f.Person.FullName)
.RuleFor(x => x.Shipment_Phone, f => f.Person.Phone)
.RuleFor(x => x.Shipment_Date, f => DateTime.UtcNow);
return data.Generate(count: 5).ToArray();
}
}
}
Build the Custom Authentication Provider
You can build your custom authentication state provider class by extending the AuthenticationStateProvider
or any of the other classes in the inheritance hierarchy. The following code snippet illustrates how you can build your custom authentication state provider class by extending the AuthenticationStateProvider
class:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
namespace Blazor_Server_App.Areas.Identity
{
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
throw new NotImplementedException();
}
}
}
The following code snippet illustrates how you can modify the GetAuthenticationAsync
method to retrieve the current user:
public async override Task<AuthenticationState>
GetAuthenticationStateAsync()
{
try
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "Joydip"),
new Claim(ClaimTypes.Role, "Admin")
}, null);
return await Task.FromResult(new AuthenticationState(new
ClaimsPrincipal(identity)));
}
catch(Exception ex)
{
Console.WriteLine("Error retrieving authentication state:"
+ ex.ToString());
}
return null;
}
The following code snippet shows how you can implement a method named Logout
. This method will be used to log out the current logged-in user in your application:
public async Task<AuthenticationState> Logout()
{
this.CurrentUser = null;
return await GetAuthenticationStateAsync();
}
Register your CustomAuthenticationStateProvider
class in the Program.cs
file using the following piece of code:
builder.Services.AddScoped
<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
The complete source code of the CustomAuthenticationProvider
class is given in Listing 9.
Listing 9: The CustomAuthenticationStateProvider class
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private ClaimsPrincipal CurrentUser { get; set; }
public async override Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "Joydip"),
new Claim(ClaimTypes.Role, "Admin")
}, null);
return await Task.FromResult(new AuthenticationState(new
ClaimsPrincipal(identity)));
}
catch (Exception ex)
{
Console.WriteLine("Error retrieving authentication state:"
+ ex.ToString());
}
return null;
}
}
Create the AuthenticationController Class
You need an API endpoint to log in and log out an existing user and also register a new user. To achieve this, you'll create an API controller in the server project named AuthentcationController
. The actions methods of this controller
class will take advantage of the ASP.NET Core Identity Framework.
The following code snippet illustrates the Login
action method that can be used to log an existing user into the application.
[HttpPost]
public async Task<IActionResult> Login(LoginRequest request)
{
var user = await _userManager.FindByNameAsync(request.UserName);
if (user == null)
return BadRequest("The user does not exist");
var result = await _signInManager.CheckPasswordSignInAsync(user,
request.Password, false);
if (!result.Succeeded)
return BadRequest("Invalid password");
await _signInManager.SignInAsync(user, true);
return Ok(request.UserName);
}
The Register
action method given below is used to register a new user in the application.
[HttpPost]
public async Task<IActionResult> Register(RegisterRequest registerRequest)
{
var user = new ApplicationUser();
user.UserName = registerRequest.UserName;
var result = await _userManager.CreateAsync(user, registerRequest.Password);
if (!result.Succeeded)
return BadRequest("Error registering user");
return Ok(registerRequest.UserName);
}
The Logout
action method will be used to log out an already logged-in user in the application.
[Authorize]
[HttpPost]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return Ok();
}
The complete source code of the AuthenticationController
class is given in Listing 10.
Listing 10: The AuthenticationController class
using BillingSystem.Server.Models;
using BillingSystem.Shared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace BillingSystem.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public AuthenticationController(UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost]
public async Task<IActionResult> Login(LoginRequest request)
{
var user = await _userManager.FindByNameAsync(request.UserName);
if (user == null)
return BadRequest("The user does not exist");
var result = await _signInManager.CheckPasswordSignInAsync(user,
request.Password, false);
if (!result.Succeeded)
return BadRequest("Invalid password");
await _signInManager.SignInAsync(user, true);
return Ok(request.UserName);
}
[HttpPost]
public async Task<IActionResult> Register(RegisterRequest
registerRequest)
{
var user = new ApplicationUser();
user.UserName = registerRequest.UserName;
var result = await _userManager.CreateAsync(user,
registerRequest.Password);
if (!result.Succeeded)
return BadRequest("Error registering user");
return Ok(registerRequest.UserName);
}
[Authorize]
[HttpPost]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return Ok();
}
}
}
Add Authentication and Authorization Middleware to Your Application
Ensure that you add the following two statements in the Program.cs
file to add the Authentication and Authorization middleware to the request processing pipeline.
app.UseAuthentication();
app.UseAuthorization();
The complete source code of the Program.cs
file is given in Listing 11.
Listing 11: The Program.cs file
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using BillingSystem.Server.Data;
using BillingSystem.Server.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<ApplicationUser,
IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
}).AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddScoped<IShipmentRepository, ShipmentRepository>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Secure Blazor Applications Using Anti-forgery Tokens
By using anti-forgery tokens, you can prevent CRSF attacks on your ASP.NET Core Blazor applications. If you're using Blazor WebAssembly with ASP.NET Core Identity framework, you should implement anti-forgery tokens in your HttpPost
action methods, as shown in the code snippet given below:
[HttpGet("getantiforgerytoken")]
public IActionResult GetAntiforgeryToken()
{
var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
return new ObjectResult(new
{
requestToken = tokens.RequestToken
});
}
Then retrieve this token at the client side of your Blazor WebAssembly application, store it, and pass this token in all subsequent HTTP POST requests. If you're using a Blazor Server application, pass an anti-forgery token each time a form is submitted.
Best Practices
Safeguarding your Blazor applications is imperative in averting data breaches and ensuring data security. To secure your Blazor applications efficiently, you need consistent updates and strategies for addressing specific security concerns. Follow the recommended practices to enable your developers to design reliable and secure Blazor applications that are resilient to known vulnerabilities and threats.
Here are the key strategies that can be implemented to secure your Blazor efficiently:
- Storing API Keys: API keys should never be included in the application code. Instead, they should be stored in environment variables or secure configuration files. It's a recommended practice to use user secrets while developing your application and using Azure Key Vault to store them when your application is live in production.
- Update and patch regularly: Always keep Blazor and other dependencies updated to the newest version, ensuring their security optimizations and changes are correctly applied. Also monitor any third-party libraries or frameworks that your application uses and update them at regular intervals.
- Protect your APIs: To safeguard your application's APIs, consider using HTTPS and enforcing strict data validation for inputs. Also implement CORS policies to restrict access to your APIs and use an appropriate authentication mechanism, such as token-based authentication, to secure your API endpoints. You can also implement rate-limiting to prevent API abuse, update the Blazor assemblies regularly, and implement appropriate logging and monitoring strategies to detect any security issues.
- Using authentication and authorization: Authentication aims to verify that a person is who they claim to be, i.e., that the credentials they use for logging into the system are genuine and can be trusted. You can choose strategies such as JWT tokens, OAuth tokens, or API keys to implementing authentication. Authorization refers to the process by which the application determines what resources are accessible to an authenticated user and what information they can access within the application. You can use several techniques to implement authorization, such as attribute-based or role-based access control systems.
- Monitor and log: Take advantage of a comprehensive approach to handling exceptions in your application. Your logs should capture key details required for diagnosing problems in your code while avoiding sensitive information. Also review the logs for suspicious activities that may present a security problem. To log data, you may use the built-in logging capabilities provided by ASP.NET Core or you can integrate a more advanced and versatile logging framework, such as Serilog.
- Input validation: Another way to protect APIs is by verifying that the input data, i.e., the data entered by the user is valid and satisfies specific requirements. To secure your API, you must validate the input data, i.e., the user's entered data. This helps you protect your API from injection attacks that include cross-site scripting (XSS) and SQL Injection.
- Data protection: Take advantage of the ASP.NET Core Data Protection API to protect sensitive information in your application. Also use strong hashing algorithms to hash passwords. Never store sensitive information in local or session browser storage. If you have to use these storage options, always encrypt the data.
- Use HTTPS: Always use HTTPS to protect your API endpoints. Ensure that you've used appropriate access controls to safeguard sensitive data exposed by your Blazor applications. Use the HTTPS protocol to secure data in transit and ensure that API keys are updated periodically.
- Using timeouts: For several reasons, when you're working with applications that leverage APIs, the downstream services might need to be fixed, i.e., they're non-responsive. Possible causes can be because the services are processing data, it's taking quite a lot of time to read from the database, etc. Whatever the reason, don't allow your API to hang for too long while the clients wait for a response. To prevent this, you can leverage timeouts for your slow or non-responsive services.
- Secure deployment: Ensure that you have configured the server and application settings correctly and removed all debugging and error messages in the production environments. Select an appropriate deployment platform for deploying your Blazor applications such as AWS, Azure App Service, and Google Cloud, that not only supports Blazor applications but also provides security features.
Rate limiting restricts the number of requests a client may make to a sever in a particular time frame. It can help thwart denial-of-service attacks and brute-force attacks by limiting the back requests made to your APIs in a certain time period.
Where Do You Go from Here?
To guard your application against security threats and vulnerabilities and to protect your data from misuse, you can implement several strategies, such as authentication, authorization, API security, input validation, and shielding your application against XSS and CSRF attacks. The back-end-for-front-end pattern (for more about this, see my previous article) provides several security benefits to your Blazor application. It enables your server component to manage security functions, like token generation and management while blocking direct API communication.
Cross-site scripting (XSS) and Cross-Site Request Forgery (CSRF) are typical examples of web security vulnerabilities. They differ in how they operate. The former enables the attacker to place and run malicious scripts within the context of victim's web browser. In the latter, the attacker deceives the victim user into executing commands the user did not mean to execute.
Implementing application security should not be a one-time exercise. Instead, it should be an ongoing process that requires you to learn and adapt new tools, technologies, techniques, and security guidelines. Finding security flaws or vulnerabilities within applications that attackers can exploit isn't as simple as it seems. You can adhere to the recommended guidelines and devise a strategy to implement strong security and shield your application from prying eyes. With the right mindset and appropriate security strategies, you can build secure and scalable Blazor applications.