Get Context.User.Identity from signalr hub in blazor server app (.NET 7)

850 views Asked by At

I would like to understand why Context.User.Identity.Name is null and why Context.User.Identity.IsAuthenticated is false inside a signalr Hub in the onConnectedAsync method? Msdn says:

"SignalR can be used with ASP.NET Core authentication to associate a user with each connection...

In a browser-based app, cookie authentication allows existing user credentials to automatically flow to SignalR connections. When using the browser client, no extra configuration is needed. If the user is logged in to an app, the SignalR connection automatically inherits this authentication."

Project code available here :

https://github.com/nl20121974/CC

Précision : in this case, user was previously authenticated with the standard authentication form against membership individual accounts (database username + password) from the blazor app. I started with the Microsoft blazor chat sample. This one :

https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-tutorial-build-blazor-server-chat-app

The user logs in first from the authentication form with credentials (username + password) from database (.net membership) and goes to the chat page where the user name is automatically sent to chat hub when the "Chat" button is clicked.

enter image description here

enter image description here

enter image description here

enter image description here

I would like to fill a connected users list from signalr hub directly

I searched on Msdn blazor and signalr documentation and Googled a lot.

2

There are 2 answers

6
Bisjob On

I had the same issue. The problem is when you hook the request in your hub, there is no JWT token. (If you use JWT token also)

(I'm in .NET 6)

So you need to create a PostConfigureOptions class:

public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
    public void PostConfigure(string name, JwtBearerOptions options)
    {
        var originalOnMessageReceived = options.Events.OnMessageReceived;
        options.Events.OnMessageReceived = async context =>
        {
            await originalOnMessageReceived(context);

            if (string.IsNullOrEmpty(context.Token))
            {
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;

                if (!string.IsNullOrEmpty(accessToken) &&
                    path.StartsWithSegments("/hub"))
                {
                    context.Token = accessToken;
                }
            }
        };
    }
}

in you Program.cs :

services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerOptions>());

Everything is detailled here : SignalR authentication & authorization

I also created a AppClaimsPrincipalFactory. But I'm not sure you would need this:

public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    public AppClaimsPrincipalFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> optionsAccessor)
        : base(userManager, roleManager, optionsAccessor)
    { 

    }

    public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
        var principal = await base.CreateAsync(user);
        if (!string.IsNullOrWhiteSpace(user.FirstName))
            ((ClaimsIdentity)principal.Identity).AddClaims(new[] {new Claim(ClaimTypes.GivenName, user.FirstName)});
            
        if (!string.IsNullOrWhiteSpace(user.LastName))
            ((ClaimsIdentity)principal.Identity).AddClaims(new[] {new Claim(ClaimTypes.Surname, user.LastName)});

        if (!string.IsNullOrWhiteSpace(user.Email))
            ((ClaimsIdentity)principal.Identity).AddClaims(new[] { new Claim(ClaimTypes.Email, user.Email) });

        return principal;
    }
}



services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
2
مهدی On

Get username in blazor .Net6 .Net8

in page:

    @using BlazorApp1.Data
    @using Microsoft.EntityFrameworkCore
    @inject AuthenticationStateProvider GetAuthenticationStateAsync

@email


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