In this part of the article, we have to accomplish our targets like:
- User Registration Form.
- Password Hashing
- User Registration Logic.
Create Asp.Net Core Areas Folder:
We are going to create Razor Pages for our User Registration, so let store them in the 'Areas' folder. So let's create 'Areas' folders and also add the 'Layout' template and a few other additional configurations.
Now let's create folders into our application like 'Areas\Identity\Pages\Account', 'Areas\Identity\Pages\Shared'.
Areas/Identity/Pages/Shared/_Layout.cshtml:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="/css/bootstrap/bootstrap.min.css" /> </head> <body> <nav class="navbar navbar-dark bg-primary"> <div class="container-fluid"> <span class="navbar-brand mb-0 h1">Navbar</span> </div> </nav> @RenderBody() </body> </html>Let's create a new '_ViewImports.cshtml' file into the 'Areas' to register the razor tag helper and then to register the namespaces.
Areas/Identity/Pages/_ViewImports.cshtml:
@using Dot6.Bserver.Cookie.Auth.Areas.Identity.Pages.Account @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpersLet's create a new '_ViewStart.cshtml' file into the 'Areas' in which will specify the path to our newly created layout.
Areas/Identity/Pages/_ViewStart.cshtml:
@{ Layout = "/Areas/Identity/Pages/Shared/_Layout.cshtml"; }
Create Registration From Model:
Now let's create a model for form binding like 'Models/Auth/RegiserVm.cs'.
Models/Auth/RegisterVm.cs:
namespace Dot6.Bserver.Cookie.Auth.Models.Auth; public class RegisterVm { public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string Password { get; set; } public string ConfirmPassword { get; set; } }
Implement User Registration Logic:
Let's implement 'User Registration' logic, so lets create files like 'AccountLogic', 'IAccountLogic'.
Logic/IAccountLogic.cs:
public interface IAccountLogic { }Logic/AccountLogic.cs:
using Dot6.Bserver.Cookie.Auth.Data; namespace Dot6.Bserver.Cookie.Auth.Logic; public class AccountLogic : IAccountLogic { private readonly MyCookieAuthContext _myCookieAuthContext; private readonly IHttpContextAccessor _accessor; public AccountLogic(MyCookieAuthContext myCookieAuthContext, IHttpContextAccessor accessor) { _myCookieAuthContext = myCookieAuthContext; _accessor = accessor; } }
- Here injected 'MyCookieAuthContext' and 'IHttpContextAcessor'.
Program.cs:
builder.Services.AddScoped<IAccountLogic, AccountLogic>(); builder.Services.AddHttpContextAccessor();Let's add logic for validating our user registration model.
using System.Text.RegularExpressions; private string ResigstrationValidations(RegisterVm registerVm) { if (string.IsNullOrEmpty(registerVm.Email)) { return "Eamil can't be empty"; } string emailRules = @"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"; if (!Regex.IsMatch(registerVm.Email, emailRules)) { return "Not a valid email"; } if (_myCookieAuthContext.Users.Any(_ => _.Email.ToLower() == registerVm.Email.ToLower())) { return "user already exists"; } if (string.IsNullOrEmpty(registerVm.Password) || string.IsNullOrEmpty(registerVm.ConfirmPassword)) { return "Password Or ConfirmPasswor Can't be empty"; } if (registerVm.Password != registerVm.ConfirmPassword) { return "Invalid confirm password"; } // atleast one lower case letter // atleast one upper case letter // atleast one special character // atleast one number // atleast 8 character length string passwordRules = @"^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!*@#$%^&+=]).*$"; if (!Regex.IsMatch(registerVm.Password, passwordRules)) { return "Not a valid password"; } return string.Empty; }
- (Line: 5-8) Checking user email address empty or not.
- (Line: 10-14) Email validation pattern check.
- (Line: 16-19) Checking email against the database to confirm the user is already registered or not.
- (Line: 21-25) Checking 'Password' or 'ConfirmPassword' empty or not.
- (Line: 27-30) Verifying the 'Password' and 'ConfirmPassword' matching or not.
- (Line: 32-41) Password validation pattern check.
Logic/AccountLogic.cs:
private string PasswordHash(string password) { byte[] salt = new byte[16]; new RNGCryptoServiceProvider().GetBytes(salt); var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 1000); byte[] hash = pbkdf2.GetBytes(20); byte[] hashBytes = new byte[36]; Array.Copy(salt, 0, hashBytes, 0, 16); Array.Copy(hash, 0, hashBytes, 16, 20); return Convert.ToBase64String(hashBytes); }
- (Line: 3) Initialized 16-byte array variable to store salt key for password hashing.
- (Line: 4) Using 'System.Security.Cryptography.RNGCryptoServiceProvider' generating random salt key value.
- (Line: 6) Using 'System.Security.Cryptography.Rfc2898DeriveBytes()' we hashing our passing by using our salt. Here value '1000' represents the iteration count for encrypting our password.
- (Line: 7) Taking 20 bytes of data as our password byte value.
- (Line: 9-13) Here we can observe that our salt value and password byte value are concatenated and stored as finally generated password hash.
Logic/AccountLogic:
public async Task<(bool Success, string Message)> UserRegistrationAsync(RegisterVm register) { string message = ResigstrationValidations(register); if (!string.IsNullOrEmpty(message)) { return (false, message); } Users newUser = new(); newUser.Email = register.Email; newUser.FirstName = register.FirstName; newUser.LastName = register.LastName; newUser.PasswordHash = PasswordHash(register.Password); _myCookieAuthContext.Users.Add(newUser); await _myCookieAuthContext.SaveChangesAsync(); var role = await _myCookieAuthContext.Roles.Where(_ => _.Name.ToUpper() == "USER") .FirstOrDefaultAsync(); if (role != null) { UserRoles userRoles = new(); userRoles.RoleId = role.Id; userRoles.UserId = newUser.Id; _myCookieAuthContext.UserRoles.Add(userRoles); await _myCookieAuthContext.SaveChangesAsync(); } return (true, string.Empty); }
- (Line: 3-7) Checking user registration model. If the model is invalid then terminates execution by returning the error message.
- (Line: 13) Fetching password hash value.
- (Line: 15-16) Saving the user details into the database.
- (Line: 18-19) Fetching the 'User' role which we are going to assign to the user as default on registering.
- (Line: 21-29) Saving the user-specific roles into the 'UserRoles' database.
Let's define our method definition into the 'IAccountLogic'.
Logic/IAccountLogic:
Task<(bool Success, string Message)> UserRegistrationAsync(RegisterVm register);
Create Registration Razor Pages:
Let's create our user registration razor pages like 'Register.cshtml', 'Register.cshtml.cs' files.
Area/Identity/Pages/Account/Register.cshtml.cs:
using Dot6.Bserver.Cookie.Auth.Models.Auth; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace Dot6.Bserver.Cookie.Auth.Areas.Identity.Pages.Account; public class RegisterModel : PageModel { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IAccountLogic _accountLogic; public RegisterModel(IHttpContextAccessor httpContextAccessor, IAccountLogic accountLogic) { _httpContextAccessor = httpContextAccessor; _accountLogic = accountLogic; } [BindProperty] public RegisterVm RegisterForm { get; set; } public string ErrorMessage { get; set; } public bool IsUserRegistrationSuccessfull { get; set; } public async Task<IActionResult> OnGetAsync() { if (_httpContextAccessor.HttpContext.User.Identity.IsAuthenticated) { return Redirect("/"); } return Page(); } public async Task<IActionResult> OnPostAsync() { var registration = await _accountLogic.UserRegistrationAsync(RegisterForm); if (!registration.Success) { ErrorMessage = registration.Message; } else { IsUserRegistrationSuccessfull = true; } return Page(); } }
- (Line: 11-12) Injected 'IHttpContextAccessor', 'IAccountLogic' into the constructor.
- (Line: 18-19) The 'RegisterVm' property is used for the form model binding. Model binding is enabled by decorating the property with the 'BindProperty'.
- (Line: 21) The 'ErrorMessage' property displays the form validation error message.
- (Line: 23) The 'IsUserRegistrationSucceful' property is used to display the registration success message.
- (Line: 25-36) The 'OnGetAsync' method gets invoked for the HTTP GET requests. This means it will load the registration form.
- (Line: 27-30) Here we are restricting the authenticated user to not accessing the registration form.
- (Line: 34-46) The 'OnPostAsync' method gets invoked by the post request(eg: registration form submission).
- (Line: 36) Invoking the user registration method like 'UserRegistrationAsync()'.
Area/Identity/Pages/Account/Register.cshtml.cs:
@page "/identity/account/register" @model RegisterModel <div class="container"> <div class="row text-center"> <div class="col"> @if (!string.IsNullOrEmpty(Model.ErrorMessage)) { <div class="alert alert-danger" role="alert"> @Model.ErrorMessage </div> } else if (Model.IsUserRegistrationSuccessfull) { <div class="alert alert-success" role="alert"> Registered Successfuly, now click here to login </div> } </div> </div> <div class="row"> <div class="col-md-6 offset-md-3"> <form method="POST"> <legend>User Registration</legend> <div class="mb-3"> <label for="txtEmail" class="form-label">Email</label> <input asp-for="RegisterForm.Email" type="text" class="form-control" id="txtEmail" /> </div> <div class="mb-3"> <label for="txtFirstName" class="form-label">First Name</label> <input asp-for="RegisterForm.FirstName" type="text" class="form-control" id="txtFirstName" /> </div> <div class="mb-3"> <label for="txtLastName" class="form-label">Last Name</label> <input asp-for="RegisterForm.LastName" type="text" class="form-control" id="txtLastName" /> </div> <div class="mb-3"> <label for="txtPassword" class="form-label">Password</label> <input asp-for="RegisterForm.Password" type="password" class="form-control" id="txtPassword" /> </div> <div class="mb-3"> <label for="txtConfirmPassword" class="form-label">Confirm Password</label> <input asp-for="RegisterForm.ConfirmPassword" type="password" class="form-control" id="txtConfirmPassword" /> </div> <button type="submit" class="btn btn-primary">Register</button> </form> </div> </div> </div>(1)Registration Form:
(2)Registration form with an error message
(3)Successful registration message.
Support Me!
Buy Me A Coffee
PayPal Me
Video Session:
Wrapping Up:
Hopefully, I think this article delivered some useful information on Blazor Server Cookie Authentication. using I love to have your feedback, suggestions, and better techniques in the comment section below.
Comments
Post a Comment