Home > Back-end >  Get current logged in user in the Service in Blazor Server without Authentication State Provider
Get current logged in user in the Service in Blazor Server without Authentication State Provider

Time:10-28

I use this repo to implement authentication and authorization with cookie on the Blazor Server.

Suppose that I'd like to retrieve the current logged-in user in the DeleteHotelRoomAsync method in HotelRoomService.cs to log the information of the user who deleted a room.

public async Task<int> DeleteHotelRoomAsync(int roomId)
    {
        var roomDetails = await _dbContext.HotelRooms.FindAsync(roomId);
        if (roomDetails == null)
        {
            return 0;
        }

        _dbContext.HotelRooms.Remove(roomDetails);
        //ToDo
        //_dbContext.DbLog.Add(userId,roomId);
        return await _dbContext.SaveChangesAsync();
    }

I can't use of AuthenticationStateProvider as it is there or there, becuase of cookie based system and so the AuthenticationStateProvider is null in below code.

I used HttpContextAccessor, and I could retrieve the authenticated userId as below, however, I couldn't use HttpContextAccessor because of Microsoft recommendations.

public class GetUserId:IGetUserId
{
    public IHttpContextAccessor _contextAccessor;
    private readonly AuthenticationStateProvider _authenticationStateProvider;
    public GetUserId(IHttpContextAccessor contextAccessor,AuthenticationStateProvider authenticationStateProvider)
    {
        _contextAccessor = contextAccessor;
        _authenticationStateProvider = authenticationStateProvider;
    }
    public  string Get()
    {            
        var userId = _contextAccessor.HttpContext.User.Claims.First().Value;
        return userId;

    }
}

So is there any safe ways to retrieve authenticated user info (e.g. userId) in a .cs file to log it into database logs for user audit log?

CodePudding user response:

First you should create a custom AuthenticationStateProvider

using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;

namespace BlazorServerTestDynamicAccess.Services;

public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
    private readonly IServiceScopeFactory _scopeFactory;

    public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory)
        : base(loggerFactory) =>
        _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));

    protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30);

    protected override async Task<bool> ValidateAuthenticationStateAsync(
        AuthenticationState authenticationState, CancellationToken cancellationToken)
    {
        // Get the user from a new scope to ensure it fetches fresh data
        var scope = _scopeFactory.CreateScope();
        try
        {
            var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>();
            return await ValidateUserAsync(userManager, authenticationState?.User);
        }
        finally
        {
            if (scope is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync();
            }
            else
            {
                scope.Dispose();
            }
        }
    }

    private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal)
    {
        if (principal is null)
        {
            return false;
        }

        var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value;
        if (!int.TryParse(userIdString, out var userId))
        {
            return false;
        }

        var user = await userManager.FindUserAsync(userId);
        return user is not null;
    }
}

Then register it:

 services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();

Now you can use it in your services. Here is an example of it:

public class UserInfoService 
{
    private readonly AuthenticationStateProvider _authenticationStateProvider;

    public UserInfoService(AuthenticationStateProvider authenticationStateProvider) =>
        _authenticationStateProvider = authenticationStateProvider ??
                                       throw new ArgumentNullException(nameof(authenticationStateProvider));

    public async Task<string?> GetUserIdAsync()
    {
        var authenticationState = await _authenticationStateProvider.GetAuthenticationStateAsync();
        return authenticationState.User.Identity?.Name;
    }
}

CodePudding user response:

Well, if You don't want to use AuthenticationStateProvider, and the cookie is not working you need any other method to Authenticate and Authorize the User to Delete the reservation.

I would go to handle this by some email password account managed during reservation, and then verification in this way, or just any "token".

Even if somebody doesn't want to register, when You make a reservation for Your client, you get his phone or e-mail. This way You can generate some random password and send it to the client with information that he needs to log in by phone/email and this password to manage the reservation.

Even more simple way is to generate some token as a parameter, that the user can use with deleteURL to be authenticated with it. Just store it in the database room reservation with that token. In example @page "/deleteRoom/{token}

Then You can use it this way

public async Task<int> DeleteHotelRoomAsync(string token)
{
    var roomDetails = await _dbContext.HotelRooms.Where(n=>n.deleteToken == token).FirstOrDefaultAsync();
    if (roomDetails == null)
    {
        return 0;
    }

    _dbContext.HotelRooms.Remove(roomDetails);
    //ToDo
    //_dbContext.DbLog.Add(userId,roomId);
    return await _dbContext.SaveChangesAsync();
}

Just override OnInitialized method to get that token string as component parameter.

  • Related