Customize 2FA in .NET Core MVC App Without Identity (Part -1)

Picture of Vivasoft Team
Vivasoft Team
Published on
05.06.2024
Time to Read
5 min

[rank_math_breadcrumb]

2FA in .NET Core MVC App Without Identity
Table of Contents

In this blog post, we’ll explore how to implement two-factor Authenticator authentication in a .NET Core MVC application. We’re dividing the blog into two parts. This is part one. We are also covering the second part in the next post.

Step By Step Guide to Implement Two-Factor Authenticator Authentication

Step 1: Create ASP.NET Core Web App using Visual Studio 2022

Open visual studio 2022 community and click on “create a new project” and select “ASP.NET Core Web App(Model-View-Controller) ” project and click next.

Model View Controller

In the “configure your new project”, enter the name, location, and solution name of your project and click next.
In the “Additional information” step, choose “.NET 8.0 in “Framework” dropdown, None in “Authentication Type” and click on create.

Step 2: Open appsettings.json and add following ConnectionString with your own database at the end of file:

				
					{
 "Logging": {
	"LogLevel": {
  	"Default": "Information",
  	"Microsoft.AspNetCore": "Warning"
	}
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
	"ApplicationDatabase": "Data Source=************;Initial Catalog=AuthDemoDB;Integrated Security=True;Encrypt=False;"
  }
}

				
			

Step 3: Install the NuGet packages:

1.  Microsoft.EntityFrameworkCore
2. Microsoft.EntityFrameworkCore.SqlServer
3. Microsoft.EntityFrameworkCore.Tools
4. BCrypt.Net-Next

Step 4: Adding Entities:

Create a class file with the name User in the Entities folder and then copy and paste the following code.

User.cs

				
					public class User
{
	[Key]
	public int Id { get; set; }
	public string Name { get; set; }
	public string Username { get; set; }
	public string Password { get; set; }
}

				
			

Step 5: Adding Context:

Create the Context Class AppDbContext in Entities folder for Entity Framework Code First Approach:

AppDbContext.cs

				
					using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
 
namespace TwoFactorAuth.Entities
{
	public class AppDbContext
	{
    	public class AppDBContext : DbContext
    	{
        	public AppDBContext(DbContextOptions<AppDBContext> options) : base(options)
        	{
        	}
 
        	public virtual DbSet<User> Users { get; set; }
    	}
	}
}

				
			

In ASP.NET Core, services such as the DB context must be registered with the dependency injection container. Update Program.cs with the following code:

				
					builder.Services.AddDbContext<AppDBContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("ApplicationDatabase")));
				
			

Step 6: Update Database using Migration:

Migrations are used in Entity Framework Core to apply changes to the database schema as the data model changes. This can be done using the following command in the Package Manager Console (Tools > NuGet Package Manager > Package Manager Console):

– Add-Migration InitialCreate
– Update-Database

After done update database command will execute successfully, just check the database’s table

database’s table

Step 7: Create Viewmodels RegisterViewModel, LoginViewModel in Models folder: RegisterViewModel.cs

				
					namespace TwoFactorAuth.Models
{
	public class RegisterViewModel
	{
    	public string Name { get; set; }
    	public string Username { get; set; }
    	public string Password { get; set; }
	}
}

				
			

LoginViewModel.cs

				
					namespace TwoFactorAuth.Models
{
	public class LoginViewModel
	{
    	public string Username { get; set; }
    	public string Password { get; set; }
	}
}

				
			

Step 8: Create UserService class in Services folder and add method RegisterUser, AuthenticateUser as follows:

UserService.cs

				
					using Microsoft.EntityFrameworkCore;
using TwoFactorAuth.Entities;
using TwoFactorAuth.Models;
namespace TwoFactorAuth.Services
{
	public interface IUserService
	{
    	Task<bool> RegisterUser(RegisterViewModel registerRequest);
	}
	public class UserService : IUserService
	{
    	private AppDbContext _dbContext;
    	public UserService(AppDbContext appDBContext)
    	{
        	_dbContext = appDBContext;
    	}
 
    	public async Task<bool> RegisterUser(RegisterViewModel registerRequest)
    	{
        	try
        	{
            	bool isExist = await _dbContext.Users.AnyAsync(u => u.Username == registerRequest.Username);
            	if (isExist)
            	{
                	return false;
            	}
            	User user = new User();
 
            	user.Name = registerRequest.Name;
            	user.Username = registerRequest.Username;
 
            	user.Password = BCrypt.Net.BCrypt.HashPassword(registerRequest.Password);
 
            	var res = await _dbContext.Users.AddAsync(user);
 
            	await _dbContext.SaveChangesAsync();
 
            	return true;
        	}
        	catch (Exception ex)
        	{
            	return false;
        	}
 
    	}
    	public async Task<bool> AuthenticateUser(LoginViewModel loginRequest)
    	{
        	try
        	{
            	var user = await _dbContext.Users.FirstOrDefaultAsync(m => m.Username == loginRequest.Username);
 
            	if (user != null && BCrypt.Net.BCrypt.Verify(loginRequest.Password, user.Password))
            	{
                	return true;
            	}
            	else
            	{
                	return false;
            	}
 
        	}
        	catch (Exception ex)
        	{
            	return false;
        	}
 
    	}
	}
}

				
			

To Implement password hashing BCrypt.Net.BCrypt.HashPassword & BCrypt.Net.BCrypt.Verify methods used.

Step 9: Register Userservice with the dependency injection container in program.cs file as follows:

				
					builder.Services.AddScoped<IUserService, UserService>();
				
			

Step 10 : Configure Cookie authentication in Program.cs:

				
					builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
                	{
                    	options.LoginPath = "/Account/Login/";
                	});
				
			

Step 11 : Add Controller named AccountController in Controllers folder and implement Regiser & Login action as follows:

AccountController.cs

				
					using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using TwoFactorAuth.Entities;
using TwoFactorAuth.Models;
using TwoFactorAuth.Services;
 
namespace TwoFactorAuth.Controllers
{
	public class AccountController : Controller
	{
    	private readonly IUserService _userService;
    	public AccountController(IUserService userService)
    	{
        	_userService = userService;
    	}
    	[HttpGet]
    	public async Task<IActionResult> Register()
    	{
        	return View();
    	}
    	[HttpPost]
    	public async Task<IActionResult> Register(RegisterViewModel request)
    	{
        	if (ModelState.IsValid)
        	{
            	bool registrationStatus = await _userService.RegisterUser(request);
            	if (registrationStatus)
            	{
                	ModelState.Clear();
                    TempData["Success"] = "Registration Successful!";
                	return View();
            	}
            	else
            	{
                	TempData["Fail"] = "Registration Failed.";
                	return View(request);
            	}
        	}
        	return View(request);
    	}
    	[HttpGet]
    	public async Task<IActionResult> Login()
    	{
        	return View();
    	}
    	[HttpPost]
    	public async Task<IActionResult> Login(LoginViewModel request)
    	{
        	if (ModelState.IsValid)
        	{
            	bool isAuthenticaed = await _userService.AuthenticateUser(request);
 
            	if (isAuthenticaed)
            	{
                	var claims = new List<Claim>
                	{
                    	new Claim(ClaimTypes.Name, request.Username)
                	};
                	ClaimsIdentity userIdentity = new ClaimsIdentity(claims, "login");
                	ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
 
                	await HttpContext.SignInAsync(principal);
                	return RedirectToAction("Index", "Home");
            	}
            	else
            	{
                    TempData["UserLoginFailed"] = "Login Failed.Please enter correct credentials";
                	return View(request);
            	}
        	}
        	return View(request);
    	}
 
    	[HttpPost]
    	public async Task<IActionResult> Logout()
    	{
        	await HttpContext.SignOutAsync();
        	return RedirectToAction("Index", "Home");
    	}
	}
}

				
			

Step 12 : Add Register & Login View in Account folder under Views folder as follows: Register.cshtml

				
					@model TwoFactorAuth.Models.RegisterViewModel
@{
	ViewData["Title"] = "Register";
}
<h2>Register - New User</h2>
<hr />
<div class="row">
	<div class="col-md-4">
    	@if (TempData["Success"] != null)
    	{
        	<p class="alert alert-success">@TempData["Success"]  <a asp-action="Login">Click here to login</a></p>
    	}
    	@if (TempData["Fail"] != null)
    	{
        	<p class="alert alert-danger">@TempData["Fail"]</p>
    	}
    	<form asp-action="Register">
        	<div asp-validation-summary="ModelOnly" class="text-danger"></div>
        	<div class="form-group">
            	<label asp-for="Name" class="control-label"></label>
            	<input asp-for="Name" class="form-control" />
            	<span asp-validation-for="Name" class="text-danger"></span>
        	</div>
        	<div class="form-group">
            	<label asp-for="Username" class="control-label"></label>
            	<input asp-for="Username" class="form-control" />
            	<span asp-validation-for="Username" class="text-danger"></span>
        	</div>
        	<div class="form-group">
            	<label asp-for="Password" class="control-label"></label>
            	<input type="password" asp-for="Password" class="form-control" />
            	<span asp-validation-for="Password" class="text-danger"></span>
        	</div>
        	<div class="form-group">
            	<input type="submit" value="Register" class="btn btn-primary m-2" />
        	</div>
    	</form>
	</div>
</div>
<div>
	<a asp-action="Login">Back to User Login</a>
</div>
@section Scripts {
	@{
    	await Html.RenderPartialAsync("_ValidationScriptsPartial");
	}
}
Login.cshtml
@model TwoFactorAuth.Models.LoginViewModel
@{
	ViewData["Title"] = "Login";
}
 
<h4>Login</h4>
<hr />
<div class="row">
	<div class="col-md-4">
    	@if (TempData["UserLoginFailed"] != null)
    	{
        	<p class="alert alert-danger">@TempData["UserLoginFailed"]</p>
    	}
 
    	<form asp-action="Login">
        	<div asp-validation-summary="ModelOnly" class="text-danger"></div>
        	<div class="form-group">
            	<label asp-for="Username" class="control-label"></label>
            	<input asp-for="Username" class="form-control" />
            	<span asp-validation-for="Username" class="text-danger"></span>
        	</div>
        	<div class="form-group">
            	<label asp-for="Password" class="control-label"></label>
            	<input type="password" asp-for="Password" class="form-control" />
            	<span asp-validation-for="Password" class="text-danger"></span>
        	</div>
        	<div class="form-group">
            	<input type="submit" value="Login" class="btn btn-default btn-success" />
            	<a asp-action="Register" class="btn btn-info m-2">SignUp</a>
        	</div>
    	</form>
	</div>
</div>
@section Scripts {
	@{
    	await Html.RenderPartialAsync("_ValidationScriptsPartial");
	}
}

				
			

Step 13: Update _Layout page as follows:

_Layout.cshtml

				
					<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
	<title>@ViewData["Title"] - TwoFactorAuth</title>
	<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
	<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
	<link rel="stylesheet" href="~/TwoFactorAuth.styles.css" asp-append-version="true" />
</head>
<body>
	<header>
    	<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
        	<div class="container-fluid">
            	<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">TwoFactorAuth</a>
            	<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                    	aria-expanded="false" aria-label="Toggle navigation">
                	<span class="navbar-toggler-icon"></span>
            	</button>
            	<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                	<ul class="navbar-nav flex-grow-1">
                    	@if (User.Identity.IsAuthenticated)
                    	{
                        	<li class="nav-item">
                            	<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        	</li>
                    	}
                	</ul>
                	<ul class="navbar-nav ml-auto">
                    	<!-- Use ml-auto for right alignment -->
                    	@if (User.Identity.IsAuthenticated)
                    	{
                        	<ul class="navbar-nav flex-grow-1 nav-right">
                            	<li class="nav-item">
                                	<form asp-area="" asp-controller="Account" asp-action="Logout" method="post" id="logoutForm" class="form-inline">
                                    	<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
                                	</form>
                            	</li>
                        	</ul>
 
                    	}
                    	else
                    	{
                        	<ul class="navbar-nav flex-grow-1 nav-right">
                            	<li class="nav-item">
                                	<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Login">Login</a>
                            	</li>
                            	<li class="nav-item">
                                	<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Register">Register</a>
                            	</li>
                        	</ul>
                    	}
                	</ul>
            	</div>
        	</div>
    	</nav>
	</header>
	<div class="container">
    	<main role="main" class="pb-3">
        	@RenderBody()
    	</main>
	</div>
 
	<footer class="border-top footer text-muted">
    	<div class="container">
        	&copy; 2024 - TwoFactorAuth - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
    	</div>
	</footer>
	<script src="~/lib/jquery/dist/jquery.min.js"></script>
	<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
	<script src="~/js/site.js" asp-append-version="true"></script>
	@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

				
			

Step 14 : Add Authorize Attribute on HomeController:

Run the application, it’s redirect to login page.

Signup and Registration page

By clicking SignUp button or Register menu on top navigate to register page:

register page

Input required info and click register button. Will save the user info.

two factor authentication

Now navigate to the login page and input username , password and click the Login button.

input username and password

After successful login. Home page will be shown:

Home page after successfull

If you need assistance with implementing two-factor authentication or any other .NET development tasks, Hire Dedicated .NET Developers for Your Project.

We offer expert services on specific requirements, ensuring your project is delivered with the highest quality and efficiency.

Contact us today to discuss your needs and let us provide you with a seamless solution.

50+ companies rely on our top 1% talent to scale their dev teams.
Excellence Our minimum bar.
It has become a prerequisite for companies to develop custom software.
We've stopped counting. Over 50 brands count on us.
Our company specializes in software outsourcing and provides robust, scalable, and efficient solutions to clients around the world.
klikit

Chris Withers

CEO & Founder, Klikit

Klikit-logo
Heartfelt appreciation to Vivasoft Limited for believing in my vision. Their talented developers can take any challenges against all odds and helped to bring Klikit into life.appreciation to Vivasoft Limited for believing in my vision. Their talented developers can take any challenges.
Start with a dedicated squad in 7 days

NDA first, transparent rates, agile delivery from day one.

Where We Build the Future
Scale Engineering Without the Overhead

Elastic offshore teams that integrate with your processes and timezone.

Tech Stack
0 +
Blogs You May Love

Don’t let understaffing hold you back. Maximize your team’s performance and reach your business goals with the best IT Staff Augmentation

let's build our future together

Get to Know Us Better

Explore our expertise, projects, and vision.