2

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.

Nicolas
  • 21
  • 2
  • 3
  • Because the web app allows anonymous authentication and the users aren't authenticated. There are no connected users to list, just nameless connections – Panagiotis Kanavos Apr 04 '23 at 07:03
  • I edited my post : in this case, user was previously authenticated with the standard authenticatiion form from the blazor app. I started with the Microsoft blazor chat sample. – Nicolas Apr 04 '23 at 07:13
  • We can't guess how the web app and specifically authentication was configured. Not even what sample was used. The [Azure SignalR](https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-tutorial-build-blazor-server-chat-app) sample demonstrates the Azure SignalR service and doedn't use authentication either. The username is entered manually by the user – Panagiotis Kanavos Apr 04 '23 at 07:22
  • The [SignalR with Blazor](https://learn.microsoft.com/en-us/aspnet/core/blazor/tutorials/signalr-blazor?view=aspnetcore-7.0&tabs=visual-studio&pivots=server) sample doesn't use authentication either. Once again the username is entered manually – Panagiotis Kanavos Apr 04 '23 at 07:25
  • I update my post : 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 authenticatiion form and goes to the chat page where the user name is automatically sent to chat hub when the "Chat" button is clicked. – Nicolas Apr 04 '23 at 07:33
  • The username is entered manually by the user in the `chatroom` page, stored in the `_username` parameter. That's the older Azure SignalR example, the [SignalR with Blazor](https://learn.microsoft.com/en-us/aspnet/core/blazor/tutorials/signalr-blazor?view=aspnetcore-7.0&tabs=visual-studio&pivots=server) example uses less code to do the same things *but* also requires manual entry. – Panagiotis Kanavos Apr 04 '23 at 07:49
  • Check the [Secure ASP.NET Core Blazor Server apps](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/?view=aspnetcore-7.0&tabs=visual-studio) and SignalR's [Authentication and Authorization](https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-7.0) documentation. When you create a new Blazor Server app you can specify the authentication mechanism: Windows, Microsoft Identity (essentially Azure AD) or Individual Accounts(local database storage). The template will create wrap the main App component in an `AuthorizeRouteView` automatically – Panagiotis Kanavos Apr 04 '23 at 07:54
  • Please provide enough code so others can better understand or reproduce the problem. – Community Apr 04 '23 at 08:25
  • Updated my post. Code sample available here : https://github.com/nl20121974/CC – Nicolas Apr 05 '23 at 03:56

2 Answers2

0

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>();
Bisjob
  • 567
  • 7
  • 23
  • Hi bisjob, i updated my post, i am not using token but individual account from .net membeeship – Nicolas Apr 04 '23 at 18:59
  • Can you show us the `ConfigureServices()` method in your Startup.cs ? – Bisjob Apr 05 '23 at 10:21
  • Will dit it this evening, i updated my post this morning. The project code is here : https://github.com/nl20121974/CC – Nicolas Apr 05 '23 at 11:10
  • It seems you need to add your authCookie in the hubConnection : `_hubConnection = new HubConnectionBuilder().WithUrl(_hubUrl, o => o.Cookies = authCookie).Build();`. On Blazor wasm, I need provide a TokenProvider using `Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider` (Because I have JWT auth) – Bisjob Apr 05 '23 at 13:03
  • No it's written in msdn for my case : 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. – Nicolas Apr 05 '23 at 18:29
  • Oh ok. I don't have much experience with cookies. I saw in you program.cs that you don't add Authentication in the services `services.AddAuthentication()`, you only set `app.UseAuthorization();`. Maybe you can try to add this. It will let you change options for cookies. But as you are using the default autentication system, I'm not sure it's necessary. Sorry to not help you as much as I thought I could. If you find the reason you don't have your IdentityClaim in your hub, can you post it here ? I'm curious to know why too – Bisjob Apr 06 '23 at 09:33
0

I managed to do it in a certain way, but I'm not sure if it's the best or even a good way. Please help if there is a better way.

  1. Inject the IHttpContextAccessor in Razor Page.

     @inject IHttpContextAccessor HttpContextAccessor
    
  2. Read all cookies from IHttpContextAccessor and add them to the connection cookies.

    protected override async Task OnInitializedAsync()
    {
        var uri = new UriBuilder(Navigation.Uri);
    
        hubConnection = new HubConnectionBuilder()
            .WithUrl(Navigation.ToAbsoluteUri("/chathub"), opti =>
            {
                if (HttpContextAccessor.HttpContext != null)
                    foreach (var c in HttpContextAccessor.HttpContext.Request.Cookies)
                    {
                        opti.Cookies.Add(new Cookie(c.Key, c.Value)
                        {
                                Domain = uri.Host, // Set the domain of the cookie
                                Path = uri.Path // Set the path of the cookie
                        });
                    }
            })
            .Build();
    
        hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            messages.Add(encodedMsg);
            InvokeAsync(StateHasChanged);
        });
    
        await hubConnection.StartAsync();
    }
    
  3. Now you can decorate your hub with AuthorizeAttribute.

    [Authorize]
    public class ChatHub : Hub
    {
        public override Task OnConnectedAsync()
        {
            return base.OnConnectedAsync();
        }
    
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }
    }
    
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
  • Please do not supply the same answer to multiple questions. If the questions are potential duplicates, then the best thing to do is to close one as a duplicate of the other (though you need a minimum rep level to do so). Alternatively if the questions are different, supplied answers need to be tailored to each question. – halfer Aug 09 '23 at 11:39
  • Here is [your other answer](https://stackoverflow.com/a/76703999), which aside from the introduction, is a copy-paste duplicate. – halfer Aug 09 '23 at 11:39