Search code examples
asp.net-identityblazor-server-side

Getting the identity of the logged in user?


The is for a Blazor (server side) application. As I understand it, I have three objects for a logged in user:

  • AuthenticationState which I can get in my razor page as it's injected. This allows me to make calls to see if claims allow users to take actions or see data.
  • IdentityUser which is the ASP.NET Identity user which has their email, phone etc. I can get that using the code below.
  • User which is my app user object. User.AspUserId == IdentityUser.Id so I can read that object once I have the IdentityUser.
private async Task<IdentityUser?> GetUser()
{
    var userPrincipal = (await authenticationStateTask).User;

    var userid = userPrincipal
                     .FindFirst(u => u.Type.Contains("nameidentifier"))?
                     .Value;

    if (!string.IsNullOrEmpty(userid))
        return await UserDbContext.Users.FirstOrDefaultAsync(u => u.Id == userid);

    return null;
}

My questions are:

  1. Are these three objects what I should be using?
  2. There's a AuthenticationStateProvider.AuthenticationStateChanged event. Should I set this up and use it to re-read my IdentityUser and User object and otherwise I can keep those objects without re-reading them per page.
  3. If so, where do I store these objects as they're a per session object.
  4. Is my GetUser() code above the best way to get the IdentityUser?

Anything I'm missing in all this?


Solution

  • To answer all your questions, you need to consider where you need to consume the current user.

    In a component, that's pretty simple. You can consume the Task<AuthenticationState> cascaded parameter. It's a Task, but will almost always be in the Completed state, and return immediately.

    If however you need the user context in another service you need to use the registered AuthenticationStateProvider service.

    Here's a simple User wrapper Service that can be consumed by Services or components.

    public class UserService : IDisposable
    {
        private readonly AuthenticationStateProvider _stateProvider;
        private Task _task = Task.CompletedTask;
    
        public event EventHandler<UserChangedEventArgs>? UserChanged;
    
        public ClaimsPrincipal User { get; set; } = new ClaimsPrincipal();
    
        public UserService(AuthenticationStateProvider authenticationStateProvider)
        {
            _stateProvider = authenticationStateProvider;
            _stateProvider.AuthenticationStateChanged += this.OnUserChanged;
            // get the current user. as this is an async method we just assign it to a class variable
            _task = this.GetUserAsync(_stateProvider.GetAuthenticationStateAsync());
        }
    
        public async ValueTask<ClaimsPrincipal> GetUserAsync()
        {
            // probably complete, but we don't know for sure so always await it
            await _task;
            return User;
        }
    
        private void OnUserChanged(Task<AuthenticationState> task)
        {
            // As this is a fire and forget event we assign the new task to our class task
            // which we can await if we need to
            _task = this.GetChangedUserAsync(task);
        }
    
        private async Task GetChangedUserAsync(Task<AuthenticationState> task)
        {
            // get the user
            await this.GetUserAsync(task);
            // raise our fire and forget event now we've got the new user
            this.UserChanged?.Invoke(this, new UserChangedEventArgs(User));
        }
    
        private async Task GetUserAsync(Task<AuthenticationState> task)
        {
            // Reset the user to a new ClaimsPrincipal which is effectivity no user
            this.User = new ClaimsPrincipal();
            
            // get the state object
            var state = await task;
    
            // update the user
            if (state is not null)
                this.User = state.User;
        }
    
        public void Dispose()
             => _stateProvider.AuthenticationStateChanged -= this.OnUserChanged;
    }
    
    public class UserChangedEventArgs : EventArgs
    {
        public ClaimsPrincipal? User { get; private set; }
    
        public UserChangedEventArgs(ClaimsPrincipal? user)
            => User = user;
    }
    

    For reference, here's the code for CascadingAuthenticationState:

    @implements IDisposable
    @inject AuthenticationStateProvider AuthenticationStateProvider
    
    <CascadingValue TValue="System.Threading.Tasks.Task<AuthenticationState>" Value="@_currentAuthenticationStateTask" ChildContent="@ChildContent" />
    
    @code {
        private Task<AuthenticationState>? _currentAuthenticationStateTask;
    
        /// <summary>
        /// The content to which the authentication state should be provided.
        /// </summary>
        [Parameter]
        public RenderFragment? ChildContent { get; set; }
    
        protected override void OnInitialized()
        {
            AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged;
    
            _currentAuthenticationStateTask = AuthenticationStateProvider
                .GetAuthenticationStateAsync();
        }
    
        private void OnAuthenticationStateChanged(Task<AuthenticationState> newAuthStateTask)
        {
            _ = InvokeAsync(() =>
            {
                _currentAuthenticationStateTask = newAuthStateTask;
                StateHasChanged();
            });
        }
    
        void IDisposable.Dispose()
        {
            AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged;
        }
    }
    

    You see see the original here - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/CascadingAuthenticationState.razor