Mapping SignalR connections to users - asp.net

Consider a web application like facebook, that can send realtime notifications to users.
What is the best way, using asp.net SignalR, to keep track of which connection ids belong to which user, even if the user disconnects, or reconnects later ?

Check out the following blog post:
Mapping ASP.NET SignalR Connections to Real Application Users
Briefly, you would be adding the connection ids to user on the OnConnected method and remove that connection on the OnDisconnected method. Keep in mind that an application user can have multiple connections. So, you need to have a one to many relationship between your user and the connection ids. The above linked post explains it in details with a sample.

I did this for an internal app. The way I did it is that when a user connects, I have the server ask the user to register themselves. This way I know that not only a user is connected and their signalR connnectionID, but they can also tell me any other information (like username or whatever).
When they reconnect I ask them to do it again.
SignalR will maintain the same connectionID per client even if they reconnect which is nice. A reconnection is not the same as an initial connection. New connections indicate a new client, but a reconnection is on the same client.
In my app I maintained a seperate threadsafe dictionary that I kept track of which user and which connectionID was doing what. This way I can say "oh, send message to user ABC" and look up their connectionID. Then act on the Hub's clients object in signalR for that connectionID. If you do it this way you can even have the same "user" in mutliple connections. Imagine user "abc" is open in two browser tabs. If you went strictly by connectionID they'd be technically two different users. But, by maintaining some sort of local collection grouping users and connections you can now have multiple connections for the same user.
I should mention that if you do it this way, you should make sure your site handles what happens when it restarts and loses all the connection information. For me, when someone reconnects I ask them to again re-identify themselves. This way I can re-build my local dictionary when the server comes online without worry. It does have more overhead because now you are asking all your clients to send information to you, but depending on your user case this could be staggered or bunched or otherwise distributed to help you handle load.
In general, however you get local information (whether by asking the user to supply it), or by http context session info, you need to track it yourself.

Well I used a different approach, I extended the ApplicationUser class like that:
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
//public int ApplicationUserId { get; set; }
//public string Name { get; set; }
//public string Address { get; set; }
//public string City { get; set; }
//public string State { get; set; }
//public string Zip { get; set; }
[Required]
public string Email { get; set; }
[Required]
public override string UserName { get; set; }
[NotMapped]
public string ConnectionId { get; set; }
[NotMapped]
public string ChattingUserConnectionId { get; set; }
//public string HomeTown { get; set; }
//public DateTime? BirthDate { get; set; }
}
And in my hub i'm doing something like that:
public class ChatHub : Hub
{
#region Data Members
private static ApplicationDbContext applicationDbContext = new ApplicationDbContext();
private static UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(applicationDbContext));
private static List<ApplicationUser> connectedUsers = new List<ApplicationUser>();
And when a user connects to the chat, I get his ApplicationUser object by his user name and add it to the connectedUsers list. When he disconnects, I remove him.
I ran into some random exceptions with EF states and such which made me create the ApplicationDbContext and UserManager each time it is accessed instead of setting it in a static object:
private ApplicationUser GetCurrentUser()
{
ApplicationDbContext applicationDbContext = new ApplicationDbContext();
UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(applicationDbContext));
var userName = Context.User.Identity.GetUserName();
var user = userManager.FindByName<ApplicationUser>(userName);
return user;
}
Edit:
The hub code has some problems loading child objects of the user. This code which is also used in the asp.net template will work better, ApplicationDBContext is not needed:
private ApplicationUserManager _userManager
{
get
{
return HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
}
var user = _userManager.FindByName<ApplicationUser, string>(userName);

There's a very good article on ASP.NET's SignalR tutorial section.
I've included the introductory paragraph below.
Mapping SignalR Users to Connections
Each client connecting to a hub passes a unique connection id. You can
retrieve this value in the Context.ConnectionId property of the hub
context. If your application needs to map a user to the connection id
and persist that mapping, you can use one of the following:
The User ID Provider (SignalR 2)
In-memory storage, such as a dictionary
SignalR group for each user
Permanent, external storage, such as a database table or Azure table
storage Each of these implementations is shown in this topic. You use
the OnConnected, OnDisconnected, and OnReconnected methods of the Hub
class to track the user connection status.
UserID Provider
If you're using ASP.NET's standard membership provider (IPrincipal.Identity.Name) you can just do the following to access a client based on a user account. If you have your own user system you can create your own provider.
public class MyHub : Hub
{
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
}

Related

SignalR chat members don't get existing array of users in chat

SignalR Chat - based on:
jQuery 3.4.1
SignalR 2.2.2
Asp.Net Framework Web Application
I'm trying to maintain a list of who is connected in the chat. It's a list of 'user' objects, the user object being the container to store 'name' and their assigned 'color' (red, blue, or green, etc color of text)
So the server model is an object with a List member:
public class GroupMemberModel
{
[JsonProperty("name")]
public string UserName { get; set; }
[JsonProperty("color")]
public string Color { get; set; }
[JsonProperty("avatar")]
public string AvatarImagePath { get; set; }
}
public class MessageModel
{
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("user")]
public GroupMemberModel User { get; set; }
[JsonProperty("group")]
public List<GroupMemberModel> ChatGroup { get; set; }
}
Then when the user sends a message, the server checks if this new user is in the list, and adds it if not:
public void Send(MessageModel clientModel)
{
if (!clientModel.ChatGroup.Contains(clientModel.User))
{
clientModel.ChatGroup.Add(clientModel.User);
clientModel.ChatGroup.Sort();
}
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(clientModel);
}
My issue is that anytime a new user joins, their initial 'knowledge' of the ChatGroup list of GroupMemberModel users is empty. So when a new user joins, the of 'participants' is always replaced with the single user who just joined... And this also happens between users already in the chat - the last person who left a message is the only person in the 'Participants' list.. So the list isn't being maintained at all.
So do I need to serialize / de-serialize the List ChatGroup member of MessageModel each time the server serverHub 'send' function is hit?
I thought SignalR would manage the objects on the server somehow without hitting a storage mechanism? I guess they are unique to each server call and that's why they're empty for every user? Hmmm - maybe the list has to be static?
SignalR manages users internally in its own group structures. You can't access it directly but just through a few accessors they provide that won't do what you want.
I did something very similar.
For the List, I used a static dictionary. It has to be static or a Singleton since a new hub is created for every connection. A Hub is like a webpage where a new instance is created when the client connects and destroyed when they disconnect. Any variable local to the Hub is created and destroyed with it.
static readonly ConcurrentDictionary<string, GroupMemberModel> Members; // string is the connection ID that is unique for every connection
In the Hub, I added overloads for OnConnect, OnDisconnect, and OnReconnect that check to see if the user is present and add or remove the user from the Members list. Each method will have a Context object with the data for the connected client that you can use to add or remove members.
For example:
public override Task OnConnected()
{
AddNewMember(); // add member to list, here you can check any security, header, etc.
// Note: Context contains the connected member
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
var member = GetMember();
if (member != null)
{
Members.TryRemove(member.ConnectionId, out _);
}
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
var member = GetMember(); // find the connection in the list
if (item == member)
{
var user = AddNewMember();
}
return base.OnReconnected();
}
This way, your server always maintains a list of connected members.

.NET Core SignalR: How to accomplish resource-based authorization?

All my SignalR clients connect using a JWT bearer token. I utilize the [Authorize] attribute in my SignalR Hub.
This token contains a userId which can be used to check if a user has read access on the resource through the resource's users property which contains a List<PuppyUserPermission> that look like this:
public class PuppyUserPermission
{
public string userId { get; set; }
public bool read { get; set; }
public bool write { get; set; }
}
The question is: how do I connect the dots here? Ideally, instead of something like
[Authorize]
public class PuppyHub : Hub
{
public async Task SendPuppy(Puppy pup)
{
await Clients.All.SendAsync(pup);
}
}
I would so something like the following (this is more pseudo code than anything else, as I don't use valid methods):
[Authorize]
public class PuppyHub : Hub
{
public async Task SendPuppy(Puppy pup)
{
var clients = Puppy.users.Where(u => u.read == true);
await clients.SendAsync(pup);
}
}
Basically, I'd like to ensure that the clients recieving the Puppy object via SignalR would be authorized users on the resource. Problem is, Clients is just a list of string client IDs, and I'm not sure how to go about tying them to actual users on my Puppy resource.
How do I go about achieving this?
From the beginning, I had the feeling that the answer lay in IUserIdProvider, but I didn't see how that would work for multiple users.
I finally found the answer, but it'll definitely need some cleanup.
First, create your own implementation of IUserIdProvider as follows:
public class MyUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var username = connection.User.Claims.Where(x => x.Type == "THE_CLAIM_YOU_WANT_TO_USE_TO_IDENTIFY_USERS").First().Value;
return username;
}
}
Next, register it using DI:
services.AddSingleton<IUserIdProvider, MyUserIdProvider >();
Now, when you want to send events from the server, use DI in your constructor to pull down an instance of your SignalR Hub as per usual:
private IHubContext<PuppyHub> puppyHub { get; }
public UsersController(IHubContext<PuppyHub> _puppyHub)
{
puppyHub = _puppyHub;
}
Then, where when you want to tell your clients about the new Puppy:
// ... typical controller code
// assume we have a var, puppy, with a list of authorized users
// use System.Linq to get a list of userIds where the user is authorized to read the puppy
var authorizedUsers = (IReadOnlyList<string>)puppy.users.Where(x => x.permissions.read == true).Select(i => i._id).ToList();
// send the new puppy to the authorized users
await puppyHub.Clients.Users(authorizedUsers).SendAsync("SendPuppy", puppy);
And viola! You have now done resource-based authorization with SignalR.

.NET identity user, separation of conserns

I am trying to use .NET identity.
My user has properties which are related to auth. For example, email address, password, projects user has access to, etc.
Also, the user has other fields, for example favourite color.
Because color is not related to authentication, I don't want to add color as a property of ApplicationUser : IdentityUser {}.
But somehow I need to write values into that field, so I could write a repository, which takes the regular User object. But in that case I need to write the repository, hash the password myself, so what's the point having .NET Identity?
A third solution would be to make 2 tables, one for the ApplicationUser, and one for the related properties, but we already have a database, and I don't want to change everything.
One more idea what I could think of is to have ApplicationUser : IdentityUser {} as basibally a copy of User (I can't inherit from user and IdentityUser too, and can't chain it, because of database first) and have a separate AuthenticationUser : IdentityUser in the auth. context.
How sould I do it?
thanks
I think for this issue you have to change your model like:
public class Person
{
public string Color { get; set: }
public int RoleId { get; set: }
public Role Role { get; set: }
public ICollection<UserAuthentication> UserAuthentications { get; set: }
}
public class UserAuthentication
{
public Person Person { get; set: }
public int PersonId { get; set: }
}
With these models you don't have this problem.
If you have any data, you can displace that using TSql.

Find SignalR live connections

I am using Redis backplane with SignalR. I have a problem but I couldn't find anything about this.
SignalR clients are Windows Forms application.
I'm using OnConnected and OnDisconnected events for handle user connections.
I have a POCO class for connections with ConnectionId property and some aother informations(UserName etc.).
In OnConnected event i insert new record to Redis with ConnectionId and OnDisconnected event remove the entry from Redis.
I dont'do anything in OnReconnected event.
But now there are too many records in Redis with same user names and different connectionid s.
Can i check the connection is alive with ConnectionId? If it's possible i want to delete died connections in Redis.
Do you have any idea about this same record problem?
Edit:
First, i have a mistake about OnConnected event. I don't use OnConnected event. WinForms client applications fire Join event on the Hub, after SignalR has been connected.
This is UserData POCO Class:
public class UserData
{
public string Id { get; set; }
public DateTime Connected { get; set; }
public string UserName { get; set; }
public string MachineName { get; set; }
public string ConnectionId { get; set; }
}
Join Method :
public void Join(UserData userData)
{
_trackUser.Add(userData);
}
I'm injecting _trackuser object to Hub class in Startup via
GlobalHost.DependencyResolver.Register
OnDisconnected Event :
public override Task OnDisconnected(bool b)
{
_trackUser.Remove(Context.ConnectionId);
return base.OnDisconnected(b);
}
TrackUser Add Method :
public void Add(UserData userData)
{
IRedisClient redisClient = Global.RedisClientManager.GetClient();
using (redisClient)
{
IRedisTypedClient<UserData> redisTypedClient = redisClient.As<UserData>();
UserData store = redisTypedClient.Store(userData);
}
}
TrackUser Remove Method :
public void Remove(string connectionId)
{
IRedisClient redisClient = Global.RedisClientManager.GetClient();
using (redisClient)
{
IRedisTypedClient<UserData> redisTypedClient = redisClient.As<UserData>();
bool removeEntry = redisTypedClient.RemoveEntry("urn:userdata:"+connectionId);
}
}
Keep the user connection status in a separate Database table like User table having foreign key in Connections table. Set the connection-id status in connections table from your Hub for OnConnected and OnDisconnected events.This helps to track the user connection status.
Redis backplane is purely for scaling out your application to support users connecting to different nodes over load balance environment.

Is there much authentication overhead when WebAPI makes a request to the server?

From what I understand. Every time a webapi request goes to the server then it's authenticated. My application uses WebAPI 2, Identity 2 and has get methods such as this:
/api/cityStatusList
/api/cityTypeList
/api/cityOptionList
These calls exist to get data for a page.
If the webapi is authenticating every request then should I look into how I can combine all these requests into one?
If the webapi is authenticating every request then should I look into how I can combine all these requests into one?
Why, is it causing any trouble?
You can of course define and return a class like this:
public class CityData
{
public List<CityStatus> StatusList { get; set; }
public List<CityType> TypeList { get; set; }
public List<CityOption> OptionList { get; set; }
}
Create CityView model class for your city like following :
public class CityView
{
public List<cityStatusView> cityStatusList { get; set; }
public List<cityTypeView> cityTypeList { get; set; }
public List<cityOptionView> cityOptionList { get; set; }
}
public class cityStatusView
{
public int ID { get; set; }
}
public class cityTypeView
{
public int ID { get; set; }
}
public class cityOptionView
{
public int ID { get; set; }
}
use it like following code in your web api :
// View model
var cityStatusList=..;
var cityTypeList=..;
var cityOptionList=..;
CityView objVM = new CityView();
objVM.cityStatusList = cityStatusList;
objVM.cityTypeList = cityTypeList;
objVM.cityOptionList = cityOptionList;
return ActionContext.Request.CreateResponse(HttpStatusCode.OK, objVM);
To address the question directly - yes, it is authenticating your application every time. However, on the scale of standard web-application this time is don't-you-even-worry-about-it miniscule.
Combining those routes into one might well be a good idea not because authentication has to happen multiple times, but because a web request can simply take a while. Typically this is due to the time it takes to physically send signals from the server to the client over TCP/IP (and re-send to compensate for any dropped packets). Even when parallelizing requests, fewer web requests is faster.
That being said, by default I would do the simplest thing possible and not worry about it. What I just mentioned is an optimization, should be treated as such, and not done prematurely.
As for authentication? It's just a few steps of the super-marathon that is your web request, it really doesn't matter. Someone correct me if I'm wrong, but I don't think it usually even hits the database - all it has to do is decode the claims that are stored in a cryptographically-secure fashion in the authentication cookie.

Resources