In this article, we are going to implement a 'User Login Page' in our Blazor Server application.
(3) Login Cookie
(4) Display authenticated user info.
In this part of the article, we have to accomplish our targets like:
- Login User Form.
- Login Authentication Logic.
- Configuring Cookie Authentication Service.
- Installing Blazor Authorization Package 'Microsoft.AspNetCore.Components.Authorization'.
- Adding the 'CascadingAuthenticationState' component.
- Using the 'AuthorizeView' component.
Login Password Validation:
Let's add logic to validate the user entered password in the login form against the user hash password stored in the database.
Logic/AccountLogic:
private bool ValidatePasswordHash(string password, string dbPassword) { byte[] dbPasswordHashBytes = Convert.FromBase64String(dbPassword); byte[] salt = new byte[16]; Array.Copy(dbPasswordHashBytes, 0, salt, 0, 16); var userPasswordBytes = new Rfc2898DeriveBytes(password, salt, 1000); byte[] userPasswordHash = userPasswordBytes.GetBytes(20); for (int i = 0; i < 20; i++) { if (dbPasswordHashBytes[i + 16] != userPasswordHash[i]) { return false; } } return true; }
- (Line: 1) The 'ValidatePasswordHash()' method 1st parameter 'password' value fetched the user entered in the login form and the 2nd parameter 'dbPassword'(Hashed password value) value fetched from the database.
- (Line: 3) Fetching database password hash bytes of data from the string.
- (Line: 5-6) Our Database hashed password value first 16 bytes are 'salt' key(Check password hash logic in Part-2), so let's fetch 'salt' key.
- (Line: 8-9) Since hashing means one-way encryption, we can't decrypt the password value stored in the database. So here we will hash the password from the user login form with help of the 'Salt' key.
- (Line: 11-17) Comparing the password hash byte values. If all bytes are the same password is valid else the user-entered password is invalid.
Implement User Login Logic:
Let's create a login form model like 'LoginVm.cs'.
Models/LoginVm.cs:
namespace Dot6.Bserver.Cookie.Auth.Models.Auth; public class LoginVm { public string Email { get; set; } public string Password { get; set; } }Let's add our logic for user authentication.
Logic/AccountLogic:
public async Task<string> UserLoginAsyn(LoginVm loginVm) { Users user = await _myCookieAuthContext.Users .Where(_ => _.Email.ToLower() == loginVm.Email.ToLower()) .FirstOrDefaultAsync(); if (user == null) { return "Invalid Credentials"; } if (!ValidatePasswordHash(loginVm.Password, user.PasswordHash)) { return "Invalid Credentials"; } var claims = new List<Claim>(); claims.Add(new Claim(ClaimTypes.Name, $"{user.FirstName} {user.LastName}")); claims.Add(new Claim(ClaimTypes.Email, user.Email)); var userRoles = await _myCookieAuthContext.UserRoles.Join(_myCookieAuthContext.Roles, ur => ur.RoleId, u => u.Id, (ur, u) => new { RoleId = ur.RoleId, RoleName = u.Name, UserId = ur.UserId } ) .Where(_ => _.UserId == user.Id) .ToListAsync(); foreach (var ur in userRoles) { claims.Add(new Claim(ClaimTypes.Role, ur.RoleName)); } var claimsIdentity = new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { }; await _accessor.HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); return string.Empty; }
- (Line: 3-5) Based on the user email address try to fetch user data from the database.
- (Line: 12-15) Validating user-entered passwords against our database hash passwords.
- (Line: 17-19) Adding user email and username as the authentication claims.
- (Line: 21-27) Fetching user roles from the 'UserRoles' table.
- (Line: 29-32) Adding the role claims.
- (Line: 34-35) Initialized 'System.Security.Claims.ClaimsIdentity()' and passing claims and cookie authentication scheme name as the parameters.
- (Line: 40-43) The 'SignInAsync()' method authenticates a user by creating the authentication cookie.
Logic/IAccountLogic:
Task<string> UserLoginAsyn(LoginVm loginVm);
Create Login Razor Pages:
Let's create a user Login Razor Pages like 'Login.cshtml', 'Login.cshtml.cs' files.
Areas/Identity/Pages/Account/Login.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 LoginModel : PageModel { private readonly IHttpContextAccessor _accessor; private readonly IAccountLogic _accountLogic; public LoginModel(IHttpContextAccessor accessor, IAccountLogic accountLogic) { _accessor = accessor; _accountLogic = accountLogic; } [BindProperty] public LoginVm LoginVm { get; set; } public string ErrorMessage; public async Task<IActionResult> OnGetAsync() { if (_accessor.HttpContext.User.Identity.IsAuthenticated) { return Redirect("/"); } return Page(); } public async Task<IActionResult> OnPostAsync() { ErrorMessage = await _accountLogic.UserLoginAsyn(LoginVm); if (!string.IsNullOrEmpty(ErrorMessage)) { return Page(); } return Redirect("/"); } }
- (Line: 12) Injected the 'IHttpContextAccessor' and 'IAccountLogic'.
- (Line: 17-18) The 'LoginVm' is a login form model.
- (Line: 19) The 'ErrorMessage' property to display login error messages.
- (Line: 20-27) The 'OnGetAsync' method executed for the 'HTTP GET' request that loads the user login form.
- (Line: 22-25) Restrict authenticated user from accessing the login page.
- (Line: 29-37) The 'OnPostAsync' gets invoked on submitting the login form.
- (Line: 31) Invoking the 'UserLoginAsync' method for validating user credentials.
@page "/identity/account/login" @model LoginModel <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> } </div> </div> <div class="row"> <div class="col-md-6 offset-md-3"> <form method="POST"> <legend>User Login</legend> <div class="mb-3"> <label for="txtEmail" class="form-label">Email</label> <input asp-for="LoginVm.Email" type="text" class="form-control" id="txtEmail" /> </div> <div class="mb-3"> <label for="txtPassword" class="form-label">Password</label> <input asp-for="LoginVm.Password" type="password" class="form-control" id="txtPassword" /> </div> <button type="submit" class="btn btn-primary">Register</button> </form> </div> </div> </div>
Authentication Cookie Configuration:
Now register cookie authentication service into the 'Program.cs'.
Program.cs:
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.ExpireTimeSpan = TimeSpan.FromDays(20); });
- Here defined the authentication name as 'CookieAuthenticationDefaults.AuthenticationScheme'.
- The 'ExpirationTimeSpan' controls how much time the authentication ticket stored in the cookie will remain valid from the point it is created. The expiration information is stored in the protected cookie ticket. Because of that, an expired cookie will be ignored even if it is passed to the server after the browser should have purged it.
Program.cs:
app.UseAuthentication(); app.UseAuthorization();
Install Blazor Authorization Package:
Package Manager:
Install-Package Microsoft.AspNetCore.Components.Authorization -Version 6.0.1
Install-Package Microsoft.AspNetCore.Components.Authorization -Version 6.0.1
.Net CLI:
dotnet add package Microsoft.AspNetCore.Components.Authorization --version 6.0.1
dotnet add package Microsoft.AspNetCore.Components.Authorization --version 6.0.1
CascadingAuthenticationState Component:
The blazor application can't access the HttpContext into the Blazor component. So to access the information about authenticated users into the blazor component we have to use the cascading property like 'AuthenticationState' property. So to read this 'AuthenticationState' property we have to add the 'CascadingAuthenticationState' component in the 'App.razor'.
Display User Info On Authentication:
Let's create a blazor component to display the authenticated user information.
Shared/LoginDisplay.razor:
<AuthorizeView> <Authorized> <a href="Identity/Account/Manage">Hello, @context.User.Identity?.Name!</a> </Authorized> <NotAuthorized> <a href="Identity/Account/Register">Register</a> <a href="Identity/Account/Login">Log in</a> </NotAuthorized> </AuthorizeView>
- The 'AuthorizeView' is a built-in component that helps to display the content condition based on user authentication and authorization. The 'AuthorizeView' component provides the 'context' through which we can fetch user authenticated information.
- Here we are displaying the user name if authenticated else we will show 'Login', 'Registration' links
(3) Login Cookie
(4) Display authenticated user info.
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.
Hi!
ReplyDeleteThank your for your guide. I'm wondering how to get the current user id?