ASP.NET Identity authentication using .net 6 Blazor server and Mongo DB

asp.net Identity authentication and authorization using .net 6 blazor server and mongo db

Are you ready to unlock the hidden powers of asp.net identity authentication and authorization in the world of web development? Join me on a thrilling journey as we dive into the realms of ASP.NET Identity, .NET 6 Blazor server, and the mighty Mongo DB. Let’s embark on this epic adventure together!

Create a project and add dependencies

Let’s use the terminal to create the Blazor Server Project.

dotnet new sln

Create a Project and add to the solution.

dotnet new blazorserver --name Server
dotnet sln add Server

To know more about dotnet CLI commands: click here
Open the sln file using visual studio OR open the folder in visual studio code.
I already have the visual studio 2022 community edition installed. So I am going to show it from here.

Install Mongo drivers and asp.net identity Nuget packages

  1. MongoDB.Driver – https://www.nuget.org/packages/AspNetCore.Identity.MongoDbCore/
  2. AspNetCore.Identity.MongoDbCore – https://www.nuget.org/packages/MongoDB.Driver/

Add mongo db connection and settings

Add the below configurations to the appsetting.json file.

"MongoDBSettings": {
    "ConnectionURI": "mongodb://{username}:{password}@<your-connection-URL>/",
    "Username": "testUser",
    "Pwd": "secret",
    "DatabaseName": "dbname",
    "Collection": "collectionname"
  }

Add Mongodbsetting model and configure startup.cs

Create a model named “MongoDBSettings” and add the following code:

public class MongoDBSettings
    {
        public string ConnectionURI { get; set; } = null!;
        public string UserName { get; set; } = null!;
        public string Pwd { get; set; } = null!;
        public string DatabaseName { get; set; } = null!;
        public string CategoriesCollection { get; set; } = null!;
        public string MongoDBConnectionString => $"{ConnectionURI.Replace("{username}", UserName).Replace("{password}", Pwd)}/{DatabaseName}";
    }

Let’s configure the Mongo db service in the startup.cs file

//adding mondogb service
services.Configure<MongoDBSettings>(Configuration.GetSection(nameof(MongoDBSettings)));

Let’s configure Asp.Net Identity for Mongo DB

1st Let’s Create ApplicationUser model.

using AspNetCore.Identity.MongoDbCore.Models;
using MongoDB.Bson.Serialization.Attributes;
using MongoDbGenericRepository.Attributes;

namespace Server.Data.DataModels
{
    [CollectionName("Users")]
    public class ApplicationUser: MongoIdentityUser<Guid>
    {
        [BsonElement("firstname")]
        public string FirstName { get; set; }
        [BsonElement("lastname")]
        public string LastName { get; set; }
    }
}

Creating ApplicationRole model.

using AspNetCore.Identity.MongoDbCore.Models;
using MongoDbGenericRepository.Attributes;

namespace Server.Data.DataModels;
[CollectionName("Roles")]
public class ApplicationRole : MongoIdentityRole<Guid>
{

}
Here we are extending the `MongoIdentityUser` and `MongoIdentityRole`.
This is only needed if we want some more columns to be added to these tables.
We can directly use these classes to the startup setting if we don't need any other customizations.

Now let’s add Identity settings to the startup file.


//fetching monngodb setting data
var mongoDbSettings = Configuration.GetSection(nameof(MongoDBSettings)).Get<MongoDBSettings>();
//adding identiy services to the application
services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddMongoDbStores<ApplicationUser, ApplicationRole, Guid>
                (
                    mongoDbSettings.MongoDBConnectionString, mongoDbSettings.DatabaseName
                );

Add some roles and a user by seeder

Let’s create a seeder class to insert some roles and create a user which we need to use for login.
Create a class named “AppUserSeeder.cs” and add the following code:

public class AppUserSeeder
{
    public static async void Initialize(IServiceProvider serviceProvider, IServiceScope scope)
    {
        var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
        var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<ApplicationRole>>();

        string[] roles = { "Administrator", "Subscriber" };
        
        //create roles if exists
        foreach (var role in roles)
        {
          if (!(await roleManager.RoleExistsAsync(role)))
          {
             await roleManager.CreateAsync(new ApplicationRole() { Name = role, NormalizedName = role.ToUpper() });
          }
        }

        //first admin user data
        var user = new ApplicationUser
        {
            FirstName = "Supreme",
            LastName = "Admin",
            Email = "admin@example.com",
	        NormalizedEmail = "ADMIN@EXAMPLE.COM",
            UserName = "admin",
            NormalizedUserName = "ADMIN",
            EmailConfirmed = true,
            SecurityStamp = Guid.NewGuid().ToString("D")
        };

        if (await userManager.FindByEmailAsync(user.Email) is not null) return;
        var password = new PasswordHasher<ApplicationUser>();
        var hashed = password.HashPassword(user, "secret");
        user.PasswordHash = hashed;

        var result = await userManager.CreateAsync(user);
        //assign all role to super admin
        await userManager.AddToRolesAsync(await userManager.FindByEmailAsync(user.Email), roles.Select(x=>x.ToUpper()));

    }
}

Then we need to inject the service into the startup class to be triggered when the program starts.
Inside the startup class in the “Configure” methods, add these:

var scope = app.ApplicationServices.CreateScope();
AppUserSeeder.Initialize(app.ApplicationServices, scope);

We must add “UseAuthentication” and “UserAuthorization” to the startup class.
Now the Configure method looks like below:

Configure method in startup.cs class

Create Login Page

Now we are going to create a login page using Razor views(cshtml), the reason behind that is, ASP.NET Core abstractions, such as SignInManager and UserManager, aren’t supported in Razor components. For more information on using ASP.NET Core Identity with Blazor, see Scaffold ASP.NET Core Identity into a Blazor Server app.

Now we are going to create a folder “Areas” to add Razor views. Please look into the folder structure below:

login Area Folder Structure

To create Razor Pages, right-click on the folder and click ‘New item’ and search for Razor View(Empty). And create these views following the folder structure: _IdentityLayout.cshtml

Razor view(empty)

_ViewStart.cshtml

@{
    Layout = "Shared/_IdentityLayout.cshtml";
}

_ViewImports.cshtml

@using Server
@using Server.Areas.Identity
@using Server.Areas.Identity.Pages
@using AspNetCore.Identity
@namespace Server.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

_IdentityLayout.cshtml

@namespace Server.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous" />
</head>
<body>
    @RenderBody()
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>

Now let’s create a login page using the template “Razor Page(Empty)”

Razor Page(Empty)

Adding Html to the login page:

@page 
@model Server.Areas.Identity.Pages.Account.LoginModel
@{
    ViewData["Title"] = "Login";
}

<h1>@ViewData["Title"]</h1>

<div class="row" style="margin:50px">
    <div class="col-md-4">
        <section>
            <form id="loginform" method="post">
                <h2>Use a local account to login</h2> 
                <hr />
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="form-floating" style="margin:10px">
                    <input asp-for="Input.Username" class="form-control" placeholder="Username" required="true" />
                    <label asp-for="Input.Username" class="form-label"></label>
                    <span asp-validation-for="Input.Username" class="text-danger"></span>
                </div> 
                <div class="form-floating" style="margin:10px">
                    <input asp-for="Input.Password" class="form-control" placeholder="Password" required="true" />
                    <label asp-for="Input.Password" class="form-label"></label>
                    <span asp-validation-for="Input.Password" class="text-danger"></span>
                </div>
                <div style="margin:10px">
                    <button id="btnSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
                </div>
            </form>
        </section>
    </div>
</div>

Add the below code to the “Login.cshtml.cs” file

public class LoginModel : PageModel
    {
        private readonly SignInManager<ApplicationUser> signInManager;
        private readonly UserManager<ApplicationUser> userManager;
        public LoginModel(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
        {
            this.signInManager = signInManager;
            this.userManager = userManager;
        }
        [BindProperty]
        public InputModel Input { get; set; }
        public string ReturnUrl { get; set; }

        public void OnGet()
        {
            ReturnUrl = Url.Content("~/");
        }
        public async Task<IActionResult> OnPostAsync()
        {
            if (ModelState.IsValid)
            {
                var user = await userManager.FindByNameAsync(Input.Username);
                if (user == null) return Page();
                var result = await signInManager.PasswordSignInAsync(user, Input.Password, false, false);
                if (result.Succeeded)
                    return LocalRedirect("~/dashboard");
            }
            return Page();
        }
        public class InputModel
        {
            [Required]
            public string Username { get; set; }

            [Required]
            [DataType(DataType.Password)]
            public string Password { get; set; }
        }
    }

The login page can be accessed by this URL : {your_url}/identity/account/login.

Implementing asp.net Identity authentication to work on Blazor server apps

1st we need to do some changes to the App.razor files.
Let’s wrap all the html codes inside the <CascadingAuthentizationState></CascadingAuthentizationState>, and replace the <RouteView> with <AuthorizeRouteView>.
Now we need to make the home page content to be seen by any authenticated user.

Inside your Dashboard page(or any other page), wrap the content inside <AuthorizeView> for which you want to be rendered only for any authorized user,
You can also add the below code here to show content for not authorized users.

<NotAuthorized>
        <h2 class="alert">Not Authorized</h2>
</NotAuthorized>

How to Logout

Create a new Razor page inside “Identity/Accounts” folder called “Logout.razor”.
We don’t have to do anything in the html file, just add the below code to Logout.cshtml.cs file:

public class LogoutModel : PageModel
    {
        private readonly SignInManager<ApplicationUser> signInManager;
        public LogoutModel(SignInManager<ApplicationUser> signInManager)
        {
            this.signInManager = signInManager;
        }
        public async Task<IActionResult> OnGet()
        {
            await signInManager.SignOutAsync();
             return LocalRedirect("~/identity/account/login");
        }
    }

When we redirect to the url: {your_url}/identity/account/logout, then it will call the OnGet() function and perform the logout.

References

  1. https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0
  2. https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-7.0&tabs=visual-studio#scaffold-identity-into-a-blazor-server-project
  3. https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=visual-studio
  4. https://www.mongodb.com/docs/drivers/csharp/current/
A result-oriented professional for overall 6+ years of rich experience in both backend and frontend code development and enhancement. Well-versed in skills like Asp .Net Core, MS SQL, Javascript, Html, REST API etc. An admirable team player having good communication skills and interpersonal skills with possess virtuosity resolving issue.

Leave a Reply

Your email address will not be published.

Back To Top