Artyom
Artyom

Reputation: 794

UserId in SignalR Core

I'm using SignalR with ASP.NET Core 2.0 and I'm trying to send a notification for a specific user like this:

_notification.Clients.User(id).InvokeAsync("SendMes");

where _notification is IHubContext. But it doesn't work. When I send the notification for all users, everything is fine and all users get the notification. But when I send it to a specific user, nothing happens. In connections I have needed user but it seems as if he doesn't have userId. So how can I do this? By access to Identity and claims? If so, how to do this?

Upvotes: 8

Views: 17633

Answers (7)

Rodrygo
Rodrygo

Reputation: 13

Use the Context object in the hub to get the ASP.Net Core Identity UserId.

var userId = Context.UserIdentifier;

https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-9.0#the-context-object:~:text=before%20SendAsync%20finishes.-,The%20Context%20object,-The%20Hub%20class

Upvotes: 0

Ali Borjian
Ali Borjian

Reputation: 1108

Just inject IHttpContextAccessor in your hub:

public class ChatHub : Hub
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ChatHub(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

now use it:

public async Task SendMessageToCurrentUser(string message)
{
    var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
    await Clients.User(userId).SendAsync("notify", message + "-" + userId);
}

don't forget to register it in program.cs

builder.Services.AddHttpContextAccessor();

Upvotes: 2

Nick Albrecht
Nick Albrecht

Reputation: 16938

As an addendum to the answer @Chris provided, the DefaultUserIdProvider implementation for IUserIdProvider is actually added using the TryAdd*() method.

services.TryAddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));

So all you have to do is add your own custom implementation of IUserIdProvider before calling services.AddSignalR(), and SignalR will skip adding its own when it sees yours.

services.AddSingleton<Microsoft.AspNetCore.SignalR.IUserIdProvider, MyCustomUserIdProvider>();
services.AddSignalR();

Upvotes: 5

Chris Pratt
Chris Pratt

Reputation: 239460

The user id provider defaults to using IPrincipal.Identity.Name, which for most Identity deployments, ends up being the email address. In older SignalR, this could be customized by using your own provider.

You would simply implement the following interface:

public interface IUserIdProvider
{
    string GetUserId(IRequest request);
}

And then attach it via:

GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => new MyIdProvider());

I'm not sure how much of that has changed in the Core version. IUserIdProvider still exists, though the interface has changed slightly:

public interface IUserIdProvider
{
    string GetUserId(HubConnectionContext connection);
}

When you call AddSignalR in ConfigureServices, it sets up the following, among other things of course:

services.AddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));

The DefaultUserIdProvider is obviously the default implementation. There doesn't seem to be any configuration option to override this, so if you need to use your own provider, you'll have to replace the service descriptor:

services.Replace(ServiceDescriptor.Singleton(typeof(IUserIdProvider), 
                                             typeof(MyCustomUserIdProvider)));

Obviously, that would need to come after the call to AddSignalR. Also, be aware that you must configure auth first, before configuring SignalR. Otherwise, the user will not be available to the hub.

Upvotes: 1

Hade Mohamed
Hade Mohamed

Reputation: 51

If you use JWT, you have to add the NameIdentifier Claim in the SecurityTokenDescriptor:

new Claim(ClaimTypes.NameIdentifier, userId)
           

Upvotes: 5

Artyom
Artyom

Reputation: 794

The problem was that I created my ClaimsIdentity object used with cookie like this:

new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);

Where DefaultNameClaimType was my email. When I changed 'ClaimsIdentity.DefaultNameClaimType' to 'ClaimsTypes.NameIdentifier' which is my user id, all worked correctly with this code:

_notification.Clients.User(id).InvokeAsync("SendMes");

Upvotes: 1

Francisco Goldenstein
Francisco Goldenstein

Reputation: 13787

I was facing a similar problem and the following article helped me figure it out: https://learn.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-2.1

In the Hub (server-side), I checked Context.UserIdentifier and it was null, even though the user was authenticated. It turns out that SignalR relies on ClaimTypes.NameIdentifier and I was only setting ClaimTypes.Name. So, basically I added another claim and it worked out (Context.UserIdentifier is set correctly after that).

Below, I share part of the authentication code I have, just in case it helps:

var claims = userRoles.Split(',', ';').Select(p => new Claim(ClaimTypes.Role, p.Trim())).ToList();
claims.Insert(0, new Claim(ClaimTypes.Name, userName));
claims.Insert(1, new Claim(ClaimTypes.NameIdentifier, userName)); // this is the claim type that is used by SignalR
var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);

Be careful, SignalR users and groups are case-sensitive.

Upvotes: 9

Related Questions