Implementing Custom User Authentication in ASP.NET Core MVC: A Step-by-Step Guide

In many web applications, it's necessary to create a custom user authentication system to validate user credentials against a database, rather than relying on the built-in ASP.NET Core Identity membership system. In this article, we'll explore how to implement a custom user authentication system in ASP.NET Core MVC, complete with step-by-step instructions and code examples to help you get started.

Authentication in ASP.NET Core

Implementing Login Functionality:

To implement login functionality with custom user authentication in ASP.NET Core MVC, we'll need to create a login view and handle the user's credentials in a controller action. Here's an example of how to create a login view (Login.cshtml):
@model LoginViewModel
 
<h2>Login</h2>
 
<form asp-action="Login" asp-controller="Account" method="post">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Email"></label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Password"></label>
        <input asp-for="Password" class="form-control" />
        <span asp-validation-for="Password" class="text-danger"></span>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Login</button>
    </div>
</form>
Here is the LoginViewModel class:
public class LoginViewModel
{
    [Required]
    [EmailAddress]
    public string Email { getset; }
 
    [Required]
    [DataType(DataType.Password)]
    public string Password { getset; }
}
In the corresponding controller (AccountController.cs), we'll handle the user's credentials, validate them against the database using the Entity Framework (you can use any data access here as you wish), and create an authenticated session for the user upon successful validation:
public class AccountController : Controller
{
    private readonly ApplicationDbContext _dbContext;
 
    public AccountController(ApplicationDbContext context)
    {
        _dbContext = context;
    }
 
    public IActionResult Login()
    {
        return View();
    }
 
    [HttpPost]
    public async Task<IActionResult> Login(LoginViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = _dbContext.Users.FirstOrDefault(i => i.Email == model.Email);
 
            // Check if the user exists and the password is correct
            // For the sake of simplicity, we are not using a hashing algorithm
            if (user != null && user.Password == model.Password)
            {
                // Create claims and authenticate the user
                var claims = new List<Claim>
                            {
                                new Claim(ClaimTypes.Name, user.Email),
                                new Claim(ClaimTypes.Email, user.Email),
                                // Add any additional claims as needed
                            };
 
                var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
 
                var authProperties = new AuthenticationProperties
                {
                    IsPersistent = true,
                    ExpiresUtc = DateTime.UtcNow.AddDays(7)
                };
 
                await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
 
                return RedirectToAction("Index""Home");
            }
 
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
        }
 
        return View(model);
    }
}
In the Login action method, we use the _dbContext.Users of the Entity Framework to retrieve the user based on the provided email address. If a user is found and the provided password matches the stored password, we create a ClaimsIdentity object containing the user's claims (such as their name and email). We then create a ClaimsPrincipal object from the ClaimsIdentity and call HttpContext.SignInAsync to sign in the user and create an authenticated session. 

The SignInAsync method takes three parameters: 
  1. The authentication scheme (CookieAuthenticationDefaults.AuthenticationScheme in this case). 
  2. The ClaimsPrincipal object containing the user's claims. 
  3. AuthenticationProperties to configure the authentication session (such as setting the session to persistent or non-persistent and setting the expiration time). 
After calling SignInAsync, the user will be signed in and have an authenticated session until the session expires or is manually logout.

You'll also need to configure the authentication middleware in your application's Program.cs code to enable cookie-based authentication:
//add DbContext Service
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
 
//Add Authentication Service
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/Account/Login";
    });
Add the above code before builder.Build();
app.UseAuthentication();
app.UseAuthorization();
Add the above code before builder.Run();

And you can use the [Authorize] attribute to protect certain actions or controller methods and ensure that only authenticated users can access them as shown below:
[Authorize]
public class ProductController : Controller
With these changes, the user will be properly signed in and have an authenticated session after successfully validating their credentials.

Implementing Logout Functionality:

To implement logout functionality in a custom user authentication system, we can create a logout action in the account controller that clears the user's session and redirects them to the login page or home page:
public async Task<IActionResult> Logout()
{
    // Clear the user's session
    HttpContext.Session.Clear();
 
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
 
    // Redirect to the login page or home page
    return RedirectToAction("Index""Home");
}
In this example, we simply clear the user's session by calling HttpContext.Session.Clear()

With HttpContext.SignOutAsync, you can sign the user out of the application, which invalidates their authentication cookie and effectively logs them out. After clearing the session and SignOut, we redirect the user to the Home controller's Index action.

In this comprehensive guide, we explored how to implement a custom user authentication system in ASP.NET Core MVC, including interact with the database using Entity Framework, implementing login functionality to validate user credentials and create an authenticated session, Authorizing(securing) the controller, and implementing logout functionality to clear the user's session. With the provided code examples and step-by-step instructions, you should now have a solid understanding of how to develop a custom user authentication system for your ASP.NET Core MVC applications.

No comments:

Powered by Blogger.