71

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.

Wes H
  • 4,186
  • 2
  • 13
  • 24
  • 1
    The following link is to an answer where you can learn how to access the HttpContext from a Blazor app on the initial call which is always an HTTP one, and not a WebSocket connection (SignalR) https://stackoverflow.com/a/59538319/6152891 – enet Feb 17 '20 at 18:07
  • 1
    The following link is to an answer where you can learn how to access the HttpContext from a Blazor app on the initial call which is always an HTTP one, and not a WebSocket connection (SignalR) https://stackoverflow.com/a/59538319/6152891 – enet Feb 17 '20 at 17:31
  • Take a look at: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0 – AliNajafZadeh Jun 09 '22 at 07:00
  • None of these answers worked for you? – mxmissile Mar 31 '23 at 14:43

14 Answers14

70

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.

Guy Lowe
  • 2,115
  • 1
  • 27
  • 37
Ricardo Peres
  • 13,724
  • 5
  • 57
  • 74
  • 15
    "Inject IHttpContextAccessor and from it access HttpContext and then User" . This is not true. What does a WebSocket connection have to do with HTTP requests – enet Feb 17 '20 at 22:53
  • 11
    Option 2 worked for me and was easily used to show user name in page. @authStateProvider.GetAuthenticationStateAsync().Result.User.Identity.Name – FrankCastle616 Oct 07 '20 at 15:57
  • 5
    Option 1 worked for me while on development but once published in IIS did not work. ?? So I ended using option 2 – JC Castro Mar 18 '21 at 07:50
  • 6
    1, IHttpContextAccessor -> no, no no, never! :) Without going into a "professional" explanation, practically it gives back the server's HttpContext which is shared between users!! Practically it leads some issues I run into right now: a, user infos, roles are mixed up, b, by changing a theme will be applied to every other users!! So what I learnt today: Never user IHttpContextAccessor and never do any Singleton/Static stuff! Check very carefully the injected services!! Some services registered as services.AddXYZ will register as Singleton automatically! Option 2 and 3 are fine, good advice. – István Piroska Jul 16 '21 at 22:22
  • 6
    @IstvánPiroska - that doesn't seem right. As per the documentation, the HttpContext "Encapsulates all HTTP-specific information about an individual HTTP request." This includes the user of that request if Identity is in place. If I'm incorrect, I'd like to know. Can you point to a "professional" explanation? I have always used HttpContext in the past to retrieve the current principal claim. – Macadameane Oct 19 '21 at 14:52
  • Please add the code here for option 2 – niico Jul 10 '23 at 10:03
  • @niico: please see https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/?view=aspnetcore-7.0&tabs=visual-studio#implement-a-custom-authenticationstateprovider – Ricardo Peres Jul 10 '23 at 15:27
26

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>());
asauerli
  • 386
  • 3
  • 3
17

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.

fingers10
  • 6,675
  • 10
  • 49
  • 87
  • 1
    To make it work, I had to change `OnInitializedAsync()` to `OnParametersSetAsync()` since `user.Identity.Name` was always null after sucessfully login. – Philippe Oct 03 '21 at 00:44
9

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();
}
Alex Logvin
  • 766
  • 10
  • 12
Wes H
  • 4,186
  • 2
  • 13
  • 24
  • 1
    This seems right, but I am using a base class for my component. Wont I need the directive injection in the base class as well? I am trying to do just that and calling the method on the userservice in the base constructor, but the useservice is null – bitshift Jun 30 '20 at 21:15
  • disregard, cant use it in the base class constructor. its not available at that point. – bitshift Jun 30 '20 at 21:41
  • 1
    @Wes H It doesn't work when deployed to production server environment. Can you please give alternate solution to achieve the same...? – Balagurunathan Marimuthu Feb 25 '21 at 17:51
  • 3
    This answer, provided by the asker, is wrong. I've commented on the question about it, before any answered had been provided. But folks here, including the asker, opt to ignore it. Please, read the comment by Balagurunathan Marimuthu above... – enet Sep 16 '21 at 16:41
  • 2
    As stated in the answer above do not do this. See https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-6.0 for more info. – Guy Lowe Jan 18 '22 at 04:30
9

I've had a similar requirement and have been using:

var authstate = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authstate.User;
var name = user.Identity.Name;

I already had an AuthenticationStateProvider in my startup.cs and added it to the constructor of my custom class.

Mark Cooper
  • 6,738
  • 5
  • 54
  • 92
DarrenB
  • 91
  • 1
  • 2
7

If you create a new project and choose Blazor with Windows Authentication you get a file called Shared\LoginDisplay.razor with the following content:

<AuthorizeView>
    Hello, @context.User.Identity.Name!
</AuthorizeView>

Using the <AuthorizeView> we can access @context.User.Identity.Name without any modifications on any page.

enter image description here

Ogglas
  • 62,132
  • 37
  • 328
  • 418
  • 1
    I tried this OOB and I don't get the name. I'm authenticating using ADB2C. Any Startup.cs setup needed? – Big Daddy Feb 05 '21 at 17:00
7

In your App.razor, make sure the element encapsulated inside a CascadingAuthenticationState element. This is what is generated by default if you create your Blazor project with authentication support.

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

In your component you can use the AuthenticationStateProvider to access the current user like in the following sample:

@page "/"

@layout MainLayout

@inject AuthenticationStateProvider AuthenticationStateProvider
@inject SignInManager<IdentityUser> SignInManager

@code
{
    override protected async Task OnInitializedAsync()
    {
        var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();

        if (SignInManager.IsSignedIn(authenticationState.User))
        {
            //Do something...
        }
    }
}
adt
  • 4,320
  • 5
  • 35
  • 54
Gicanu
  • 71
  • 1
  • 2
  • THIS IS BY FAR the most accurate answer, and should considered the answer, because as documented by Microsoft, they do not recommend to use httpcontext, so please ignore most of these answers, and is not their fault, is ms lack of good docs... so yes, inject SigninManager which have a UserManager property or inject UserManager directly as shown, then use userManager.GetUserId(user); (user = the user provided by authenticationstate) and it will return your data, in this case, I wanted the ID. – Guillermo Perez May 21 '23 at 02:44
  • @GuillermoPerez - are you sure? You're right of course about HttpContext, but otherwise this answer seems to be mixing the 2 methods described in the top-voted answer (#2 & #3). If you're using the CascadingAuthenticationState component, you should access AuthenticationState in your component with a CascadingParameter, no need for injection. If you're injecting AuthenticationStateProvider there's no need for CascadingAuthenticationState. – Dan Z Jun 12 '23 at 14:42
3

The below solution works only if you are running under IIS or IIS Express on Windows. If running under kestrel using 'dotnet run', please follow steps here, https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.0&tabs=visual-studio#kestrel

[startup.cs]

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();
}

[index.razor]

@page "/"
@using Microsoft.AspNetCore.Http
@inject IHttpContextAccessor httpContextAccessor

<h1>@UserName</h1>

@code {
    public string UserName;

    protected override async Task OnInitializedAsync()
    {
        UserName = httpContextAccessor.HttpContext.User.Identity.Name;
    }
}
Nemo
  • 3,285
  • 1
  • 28
  • 22
  • 1
    In addition, this seems to only work from the "main" page thread. Any asynchronous actions, including `await`, make `httpContextAccessor.HttpContext` be `null`. – Mike Dec 26 '21 at 12:57
2

I also had this problem. The best solution I found was to inject both UserManager and AuthenticationStateProvider and then I made these extension functions:

        public static async Task<CustomIdentityUser> GetUserFromClaimAsync(this 
        ClaimsPrincipal claimsPrincipal,
            UserManager<CustomIdentityUser> userManager)
        {
            var id = userManager.GetUserId(claimsPrincipal);
            return await userManager.FindByIdAsync(id);
        }

        public static async Task<CustomIdentityUser> GetCurrentUserAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
        {
            return await (await provider.GetAuthenticationStateAsync()).User.GetUserFromClaimAsync(UM);
        }

        public static async Task<string> GetCurrentUserIdAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
        {
            return UM.GetUserId((await provider.GetAuthenticationStateAsync()).User);
        }
Luk164
  • 657
  • 8
  • 22
  • 1
    According to the [docs here](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-3.1) `UserManager` and `SigninManager` aren't supported in Razor components. – aryeh Sep 22 '20 at 13:39
  • 1
    @aryeh Well they did work, no problems with compilation, no warnings either – Luk164 Sep 24 '20 at 05:30
  • 1
    It also worked for me, I'm trying to find out what it means 'not supported' since it does work. – aryeh Sep 24 '20 at 15:32
1

This was a painful journey for me chasing a moving target. In my case I only needed the user name for my Blazor component used in a Razor page. My solution required the following:

In the Index.cshtml.cs I added two properties and constructor

    public IHttpContextAccessor HttpContextAccessor { get; set; }
    public string UserName { get; set; }

    public TestModel(IHttpContextAccessor httpContextAccessor)
    {
        HttpContextAccessor = httpContextAccessor;
        if (HttpContextAccessor != null) UserName = HttpContextAccessor.HttpContext.User.Identity.Name;
    }

Then in the Index.cshtml where I add the component I called it as follows:

<component type="typeof(MyApp.Components.FileMain)" param-UserName="Model.UserName" render-mode="ServerPrerendered" />

In my component I use a code behind file (FileMain.razor.cs using public class FileMainBase : ComponentBase) have the code:

    [Parameter]
    public string UserName { get; set; } = default!;

and then as a proof of concept I added to the FileMain.razor page

    <div class="form-group-sm">
    <label class="control-label">User: </label>
    @if (UserName != null)
    {
        <span>@UserName</span>
    }
</div>
azpc
  • 500
  • 9
  • 11
1

You should add needed claims to the User after login.

For example I show the FullName on top of site (AuthLinks component) instead of email.

[HttpPost]
    public async Task<IActionResult> Login(LoginVM model)
    {
        var user = await _userManager.FindByNameAsync(model.Email);
        if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
            return Unauthorized("Email or password is wrong.");
        var signingCredentials = GetSigningCredentials();
        var claims = GetClaims(user);
        var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
        var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
        return Ok(new LoginDto { Token = token, FullName = user.FirstName + " " + user.LastName });
    }

@code {
    private LoginVM loginVM = new();

    [Inject]
    public AuthenticationStateProvider _authStateProvider { get; set; }

    private async Task SubmitForm()
    {
        var response = await _http.PostAsJsonAsync("api/accounts/login", loginVM);
        var content = await response.Content.ReadAsStringAsync();
        var loginDto = JsonSerializer.Deserialize<LoginDto>(content);
        await _localStorage.SetItemAsync("authToken", loginDto.Token);
        _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginDto.Token);
        (_authStateProvider as AuthStateProvider).NotifyLogin(loginDto.FullName);
        _navigationManager.NavigateTo("/");
    }
}

public void NotifyLogin(string fullName)
    {
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("FullName", fullName) }, "jwtAuthType"));
        var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
        NotifyAuthenticationStateChanged(authState);
    }

<AuthorizeView>
    <Authorized>
        @context.User.FindFirst("FullName")?.Value
        <button class="btn btn-outline-danger mx-4" @onclick="LogOut">LogOut</button>
    </Authorized>
    <NotAuthorized>
        <a href="account/login" class="btn btn-outline-success mx-2">Login</a>
        <a href="account/register" class="btn btn-outline-primary mx-2" style="width:123.44px">Register</a>
    </NotAuthorized>
</AuthorizeView>

enter image description here

GitHub project link: https://github.com/mammadkoma/Attendance

M Komaei
  • 7,006
  • 2
  • 28
  • 34
1

This worked for me:

Replace:

@context.User.Identity.Name

With:

@context.User.Claims.Where(x => x.Type=="name").Select(x => x.Value).FirstOrDefault()
DCripps
  • 147
  • 1
  • 1
  • 11
0

UPDATE - This answer does not work. Rather than deleting it, I've let it here as information. Please consider the other answers for the question instead.

  1. In ConfigureServices, in Startup.cs, add services.AddHttpContextAccessor();
  2. In your component class [Note: I use code-behind with null types enabled]
        [Inject]
        private IHttpContextAccessor HttpContextAccessor { get; set; } = default!;

        private string username = default!;
  1. In your component code (code behind), in protected override void OnInitialized()
        username = HttpContextAccessor.HttpContext.User.Identity.Name;

username can now be used throughout the component just like any other variable.

However, see my other answer in this question to add get the current user name from a service usable in any class.

Wes H
  • 4,186
  • 2
  • 13
  • 24
  • 13
    HttpContext shouldn't be used in Blazor app. HttpContext is HTTP-based object. Blazor Server App (client part and server part ) communicate over WebSocket connection, which lacks the concept of HTTP. Using Windows Authentication makes things worse. I don't mind that you've chosen to ignore my comment above, but users of this post must be warned, abd the sooner, the better. – enet Feb 17 '20 at 22:47
  • 1
    You don’t know what you are talking about. This has nothing to do with Windows Authentication. Blazor is served through HTTP, and the model of it that we are talking about is Server, of course. More, SignalR uses WebSockets among other protocols, like long polling, even though WebSockets is probably the typical one – Ricardo Peres Feb 18 '20 at 01:00
  • 5
    This approach worked in development on IIS Express, but resulted in nulls when deployed. AuthenticationStateProvider with GetAuthenticationStateAsync worked fine. See [Docs](https://learn.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1&tabs=visual-studio#authenticationstateprovider-service) – MichaelB May 20 '20 at 17:51
  • 5
    This doesn't work when deployed. The docs even say not to do it. – The Muffin Man May 25 '20 at 02:39
  • 1
    @MichaelB I am also having a same problem. Could you suggest a way to get claims using `AuthenticationStateProvider` as common function for all my components? In that way, we can avoid fetch claims on each component... – Balagurunathan Marimuthu Feb 25 '21 at 13:55
0

This is what works for me on a single page

Add to Startup.cs

services.AddHttpContextAccessor();

On the Razor Component page

@using Microsoft.AspNetCore.Http
@inject IHttpContextAccessor httpContextAccessor

<div>@GetCurrentUser()</div>

@code{
    protected string GetCurrentUser()
    {
        return httpContextAccessor.HttpContext.User.Identity.Name;
    }
}
Eric
  • 958
  • 1
  • 11
  • 28