Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get Current User in a Blazor component

I'm starting a new site with Blazor and Windows Authentication and need to identify the current user viewing the page/component.

For a Razor Page, the current user name can be accessed with Context.User.Identity.Name, but that doesn't seem to work in a Blazor component. I've tried injecting HttpContext into the component but the Context is null at runtime.

As a bonus, I will eventually want to incorporate this into Startup.cs so I only need to get the username once and can leverage a corporate user class (with EF Core) for my applications. Answers tailored to that use case would also be appreciated.

like image 654
Wes H Avatar asked Feb 17 '20 14:02

Wes H


People also ask

How do you get the current user in a Blazor component?

There are three possibilities for getting the user in a component (a page is a component): Inject IHttpContextAccessor and from it access HttpContext and then User ; need to register IHttpContextAccessor in Startup. ConfigureServices , normally using AddHttpContextAccessor .

How can I get current page name in Blazor?

You can get the current page title in Blazor by using the “title” property of the document object in JavaScript and by using a . JavaScript interop since there is no DOM accessibility in Blazor.

How do you reference Blazor component in code?

To capture a component reference in Blazor, use the @ref directive attribute. The value of the attribute should match the name of a settable field with the same type as the referenced component. When the parent component is rendered, the field is populated with the child component instance.

How do you beat the Blazor component model?

Begin by creating a new Blazor Server App project in Visual Studio. Then right click on the Pages folder and select Add New Item menu option. Add a new Razor Component named Message. razor to the Pages folder.


4 Answers

There are three possibilities for getting the user in a component (a page is a component):

  1. Inject IHttpContextAccessor and from it access HttpContext and then User; need to register IHttpContextAccessor in Startup.ConfigureServices, normally using AddHttpContextAccessor. Edit: according to the Microsoft docs you must not do this for security reasons.
  2. Inject an AuthenticationStateProvider property, call GetAuthenticationStateAsync and get a User from it
  3. Wrap your component in a <CascadingAuthenticationState> component, declare a Task<AuthenticationState> property and call it to get the User (similar to #2)

See more here: https://learn.microsoft.com/en-us/aspnet/core/security/blazor.

like image 118
Ricardo Peres Avatar answered Oct 21 '22 14:10

Ricardo Peres


I've now been able to get it to work with a general class, as well as a component.

To get access to the HttpContext User; in ConfigureServices, in Startup.cs add

services.AddHttpContextAccessor();

I have a CorporateUserService class for my CorporateUser class. The service class gets a DbContext through constructor injection.

I then created a new CurrentCorporateUserService that inherits from the CorporateUserService. It accepts a DbContext and an IHttpContextAccessor through constructor injection

public class CurrentCorporateUserService : CorporateUserService
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public CurrentCorporateUserService(IHttpContextAccessor httpContextAccessor,
        MyDbContext context) : base(context)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    ...

The base service class has a method GetUserByUsername(string username). The Current service class adds an additional method

public CorporateUser GetCurrentUser()
{
    return base.GetUserByUsername(_httpContextAccessor.HttpContext.User.Identity.Name.Substring(8));
}

The Current service class is registered in Startup.cs

services.AddScoped<CurrentCorporateUserService>();

Once that is done, I can use the CurrentCorporateUserService in a component with directive injection.

[Inject]
private CurrentCorporateUserService CurrentCorporateUserService { get; set; } = 
default!;

or in any class, with constructor injection.

public MyDbContext(DbContextOptions<MyDbContext> options,
    CurrentCorporateUserService CurrentCorporateUserService)
    : base(options) 
{
    _currentUser = CurrentCorporateUserService.GetCurrentUser();
}

Making it a project wide service means all my developers do not have to concern themselves with how to get the Current User, they just need to inject the service into their class.

For example, using it in MyDbContext makes the current user available to every save event. In the code below, any class that inherits the BaseReport class will automatically have the report metadata updated when the record is saved.

public override Int32 SaveChanges()
{         
    var entries = ChangeTracker.Entries().Where(e => e.Entity is BaseReport
    && (e.State == EntityState.Added || e.State == EntityState.Modified));

    foreach (var entityEntry in entries)
    {
        ((BaseReport)entityEntry.Entity).ModifiedDate = DateTime.Now;
        ((BaseReport)entityEntry.Entity).ModifiedByUser = _currentUser.Username;
    }

    return base.SaveChanges();
}
like image 26
Wes H Avatar answered Oct 21 '22 13:10

Wes H


For me the solution mentioned in the first answer 2. option worked perfect: I am using Blazor server side on .Net Core 5.0 . I injected

@inject AuthenticationStateProvider GetAuthenticationStateAsync

in my Blazor page and added the following in the code section:

protected async override Task OnInitializedAsync()
    {
        var authstate = await GetAuthenticationStateAsync.GetAuthenticationStateAsync();
        var user = authstate.User;
        var name = user.Identity.Name;
    }

In my startup.cs, I have the following lines:

services.AddScoped<ApiAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(p =>
                p.GetRequiredService<ApiAuthenticationStateProvider>());
like image 17
asauerli Avatar answered Oct 21 '22 14:10

asauerli


For blazor wasm in net 5.0 and above. Here is how I did,

  1. Wrap your <App> component inside <CascadingAuthenticationState> as shown below,
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            ...
        </Found>
        <NotFound>
            ...
        </NotFound>
    </Router>
</CascadingAuthenticationState>
  1. Then add Task<AuthenticationState> CascadingParameter inside any component as shown below,
public class AppRootBase : ComponentBase
{
    [CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
}
  1. Now you can access logged in user Identity and Claims inside component as shown below,
protected override async Task OnInitializedAsync()
{
    var authState = await authenticationStateTask;
    var user = authState.User;

    if (user.Identity.IsAuthenticated)
    {
        Console.WriteLine($"{user.Identity.Name} is authenticated.");
    }
}

Here is the reference from Microsoft docs.

like image 14
fingers10 Avatar answered Oct 21 '22 12:10

fingers10