Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4839

How to create custom authentication

$
0
0

I want to migrate my React/Java application into Blazor Server application. Therefore, I have to create custom authentication.

In Java, I use JWT Token whenever the users login using their own username and password. When I migrate it into Blazor Server, I expect to use Cookies instead.

FYI, I want to create an application that have APIs as well, so I can scale up in the future, allowing users to use APIs as well instead of only on my portal.

My backend code structure will be Service -> ControllerIn the frontend, right now for authentication, I will call API.

Here is my table Users.

[Table("USERS")]public class Users{    [Key]    [Column("id")]    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]    public long Id { get; set; }    [Column("created_by")]    public string? CreatedBy { get; set; }    [Column("created_date")]    public DateTime? CreatedDate { get; set; }    [Column("last_modified_by")]    public string? LastModifiedBy { get; set; }    [Column("last_modified_date")]    public DateTime? LastModifiedDate { get; set; }    [Column("activated")]    public bool? Activated { get; set; }    [Column("email")]    public String? Email { get; set; }    [Column("integration_token")]    public String? IntegrationToken { get; set; }    [Column("password")]    public String Password { get; set; }    [Column("user_system")]    public String? UserSystem { get; set; }    [Column("username")]    public String Username { get; set; }    [Column("iw_account_id")]    public long? IwAccountId { get; set; }    [Column("scheduled_time")]    public String? ScheduledTime { get; set; }    [Column("scheduler_enabled")]    public bool? SchedulerEnabled { get; set; }    [Column("sso_enabled")]    public bool? SsoEnabled { get; set; }    [Column("use_credit_invoice")]    public bool? UseCreditInvoice { get; set; }    [Column("extra_fee_percentage")]    public double? ExtraFeePercentage { get; set; }    [Column("use_all_in_one_invoice")]    public bool? UseAllInOneInvoice { get; set; }}

Here is my table UserRoles

[Table("USER_ROLES")]public class UserRoles{    [Key, Column("user_id", Order = 0)][ForeignKey("Users")] public long UserId { get; set; }    [ForeignKey("Roles")][Column("role_id")] public long RoleId { get; set; }    public Users Users { get; set; }    public Roles Roles { get; set; }}

AuthService.cs

public async Task<bool> ValidateCredentials(string username, string password){    var user = await _userRepository.FindByUsernameIgnoreCaseAsync(username);    if (user == null) return false;    // TODO: Implement proper password hashing and verification    //return user.Password == password;    return BCrypt.Net.BCrypt.Verify(password, user.Password);}public async Task<IEnumerable<Claim>> GetClaimsForUser(Users user){    var roles = await _userRepository.GetUserRoleNamesAsync(user.Id);    var claims = new List<Claim>    {        new Claim(ClaimTypes.Name, user.Username),        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())    };    foreach (var role in roles)    {        claims.Add(new Claim(ClaimTypes.Role, role));    }    return claims;}

CustomAuthenticationStateProvider.cs

public override async Task<AuthenticationState> GetAuthenticationStateAsync(){    var user = _httpContextAccessor.HttpContext?.User;    _logger.LogInformation($"User: {user.Identity.Name}");    if (user?.Identity?.IsAuthenticated ?? false)    {        return new AuthenticationState(user);    }    return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));}public new void NotifyAuthenticationStateChanged(Task<AuthenticationState> task){    _logger.LogInformation("Notify Authentication State Changed");    base.NotifyAuthenticationStateChanged(task);}

AuthController.cs

[HttpPost("login")]public async Task<IActionResult> Login([FromBody] LoginRequest model){    if (!(await _authService.ValidateCredentials(model.Username, model.Password)))    {        return Unauthorized(new { message = "Login unsuccessfully" });    }    var user = await _userRepository.FindByUsernameIgnoreCaseAsync(model.Username);    var claims = await _authService.GetClaimsForUser(user);    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);    var authProperties = new AuthenticationProperties    {        IsPersistent = true, // Make the cookie persistent if needed        ExpiresUtc = DateTime.UtcNow.AddMinutes(30) // Set cookie expiration    };    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,        new ClaimsPrincipal(claimsIdentity), authProperties);    return Ok(new { message = "Login successfully", user = new { user.Id, user.Username } });}

Login.razor

@page "/"@code {private async Task LoginUser(){    try    {        Logger.LogInformation("Attempting to log in user: {Username}", Input.Username);        var response = await Http.PostAsJsonAsync("/api/authenticate/login", Input);        var responseContent = await response.Content.ReadAsStringAsync();        Logger.LogInformation("API response status code: {StatusCode}", response.StatusCode);        Logger.LogInformation("API response content: {Content}", responseContent);        if (response.IsSuccessStatusCode)        {            await AuthStateProvider.GetAuthenticationStateAsync();            AuthStateProvider.NotifyAuthenticationStateChanged(                Task.FromResult(await AuthStateProvider.GetAuthenticationStateAsync())            );            await Task.Delay(2000);            NavigationManager.NavigateTo("/", true);        }        else        {            //showError = true;            StateHasChanged();        }    }    catch (Exception ex)    {        Logger.LogError(ex, "An error occurred during login for user: {Username}", Input.Username);        errorMessage = "An unexpected error occurred.";    }}}

Home.razor

protected override async Task OnParametersSetAsync(){    await Task.Delay(1000);    Logger.LogInformation("Start to get authenticate state async");    var authState = await CustomAuthStateProvider.GetAuthenticationStateAsync();    Logger.LogInformation($"authState at home.razor: {authState.User.Identity.IsAuthenticated}");    if (!authState.User.Identity.IsAuthenticated)    {        NavigationManager.NavigateTo("/login");    }}

Here is the log when user clicks sign in

info: Microsoft.Hosting.Lifetime[14]      Now listening on: https://localhost:7298info: Microsoft.Hosting.Lifetime[14]      Now listening on: http://localhost:5262info: Microsoft.Hosting.Lifetime[0]      Application started. Press Ctrl+C to shut down.info: Microsoft.Hosting.Lifetime[0]      Hosting environment: Developmentinfo: Microsoft.Hosting.Lifetime[0]      Content root path: C:\Users\oscar.le\Desktop\Oscar\MiddlewareApps\C#\Integrationsinfo: CustomAuthenticationStateProvider[0]      User:info: Integrations.Components.Pages.Home[0]      Start to get authenticate state asyncinfo: CustomAuthenticationStateProvider[0]      User:info: Integrations.Components.Pages.Home[0]      authState at home.razor: Falseinfo: CustomAuthenticationStateProvider[0]      User:info: CustomAuthenticationStateProvider[0]      User:info: Integrations.Components.Pages.App.Authentication.Login[0]      Attempting to log in user: halopsav2info: Microsoft.EntityFrameworkCore.Database.Command[20101]      Executed DbCommand (32ms) [Parameters=[@__ToLower_0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']      SELECT TOP(1) [u].[id], [u].[activated], [u].[created_by], [u].[created_date], [u].[email], [u].[extra_fee_percentage], [u].[integration_token], [u].[iw_account_id], [u].[last_modified_by], [u].[last_modified_date], [u].[password], [u].[scheduled_time], [u].[scheduler_enabled], [u].[sso_enabled], [u].[use_all_in_one_invoice], [u].[use_credit_invoice], [u].[user_system], [u].[username]      FROM [USERS] AS [u]      WHERE [u].[username] = @__ToLower_0info: Microsoft.EntityFrameworkCore.Database.Command[20101]      Executed DbCommand (6ms) [Parameters=[@__ToLower_0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']      SELECT TOP(1) [u].[id], [u].[activated], [u].[created_by], [u].[created_date], [u].[email], [u].[extra_fee_percentage], [u].[integration_token], [u].[iw_account_id], [u].[last_modified_by], [u].[last_modified_date], [u].[password], [u].[scheduled_time], [u].[scheduler_enabled], [u].[sso_enabled], [u].[use_all_in_one_invoice], [u].[use_credit_invoice], [u].[user_system], [u].[username]      FROM [USERS] AS [u]      WHERE [u].[username] = @__ToLower_0info: Microsoft.EntityFrameworkCore.Database.Command[20101]      Executed DbCommand (2ms) [Parameters=[@__userId_0='?' (DbType = Int64)], CommandType='Text', CommandTimeout='30']      SELECT [r].[name]      FROM [USER_ROLES] AS [u]      INNER JOIN [Roles] AS [r] ON [u].[role_id] = [r].[id]      WHERE [u].[user_id] = @__userId_0info: Integrations.Components.Pages.App.Authentication.Login[0]      API response status code: OKinfo: Integrations.Components.Pages.App.Authentication.Login[0]      API response content: {"message":"Login successfully","user":{"id":60014,"username":"usernametest"}}info: CustomAuthenticationStateProvider[0]      User:info: CustomAuthenticationStateProvider[0]      User:info: CustomAuthenticationStateProvider[0]      Notify Authentication State Changedinfo: CustomAuthenticationStateProvider[0]      User:info: Integrations.Components.Pages.Home[0]      Start to get authenticate state asyncinfo: CustomAuthenticationStateProvider[0]      User:info: Integrations.Components.Pages.Home[0]      authState at home.razor: Falseinfo: CustomAuthenticationStateProvider[0]      User:info: CustomAuthenticationStateProvider[0]      User:

So my workflow now is that when the user goes to my application, it will check if this user has been signed in or not, if not, it will redirect the user to /login.

If the user logins successfully, they will be able to go to homepage ("/").

You can see from the log that the API response status code is OK and API response content:

{"message":"Login successfully","user":{"id":60014,"username":"usernametest"}}.

Then it will go to Home.razor (log: Start to get authenticate state async) and call function var authState = await CustomAuthStateProvider.GetAuthenticationStateAsync() in CustomAuthenticationStateProvider, however, this function still returns nothing on User.

I don't know where I have done wrong. Hope that you guys can point out.


Viewing all articles
Browse latest Browse all 4839

Trending Articles