Reputation: 794
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
Reputation: 13
Use the Context object in the hub to get the ASP.Net Core Identity UserId.
var userId = Context.UserIdentifier;
Upvotes: 0
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
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
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
Reputation: 51
If you use JWT, you have to add the NameIdentifier Claim in the SecurityTokenDescriptor:
new Claim(ClaimTypes.NameIdentifier, userId)
Upvotes: 5
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
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