Push notifications with SignalR - c#

I am developing an application in .net core with SignalR. Users will be subscribed to the system. What I need to know is: Does a user have to be logged in to receive a notification? I would like for the notification to be pushed without them having to log in every time. It must be similar to a WhatsApp message that just "arrives". Is this possible with SignalR?

Each active browser tab is one connection to SignalR (the Client), with a unique ConnectionId. Depending on the usage of notifications, a visitor does not have to be logged in. A connection with a SignalR Hub is established when the JavaScript code has been initialized.
You can simply invoke (call) a JavaScript function from the server for each Client (visitor). So all visitors will receive the notification:
await Clients.All.SendAsync("ReceiveNotification", "Your notification message");
All connected clients will receive this 'event' from the server. Write a 'listener' for the ReceiveNotification event inside your JavaScript to do something client side:
connection.on("ReceiveNotification", function (user, message) {
// Show the notification.
});
Example
Depending on how you want to send a notification, you can either invoke the ReceiveNotification:
From the JavaScript;
connection.invoke("SendMessage", user, message).catch(function (err) {
return console.error(err.toString());
});
From the Server (e.g. a controller), using IHttpContext<THub>
public class HomeController : Controller
{
private readonly IHubContext<SomeHub> _hubContext;
public HomeController(IHubContext<SomeHub> hubContext)
{
_hubContext = hubContext;
}
public async Task<IActionResult> Index()
{
await _hubContext.Clients.All.SendAsync("ReceiveNotifiction", "Your notification message");
return View();
}
}
Example (modified) is taken from the SignalR HubContext documentation.

Related

Check in Azure Functions if client is still connected to SignalR Service

I've created negotiate and send message functions in Azure Functions (similar to the samples below) to incorporate the SignalR Service. I'm setting UserId on the SignalRMessage by using a custom authentication mechanism.
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service?tabs=csharp
[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
[SignalRConnectionInfo
(HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]
SignalRConnectionInfo connectionInfo)
{
// connectionInfo contains an access key token with a name identifier claim set to the authenticated user
return connectionInfo;
}
[FunctionName("SendMessage")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
UserId = "userId1",
Target = "newMessage",
Arguments = new [] { message }
});
}
I'd like to send a push notification if the client is no longer connected instead of adding a new object to the IAsyncCollector. I've also set up AppCenter push framework properly, but I'm facing an issue. Is there an easy way to find out which UserId is still connected to the hub? This way, I could decide to send a push. What is the recommended Microsoft guidance on this issue?
Have a look at this feature: Azure SignalR Service introduces Event Grid integration feature, where the SignalR Service emits two following event types:
Microsoft.SignalRService.ClientConnectionConnected
Microsoft.SignalRService.ClientConnectionDisconnected
More details here.

How to create custom authentication mechanism based on HTTP header?

I'm leaving old version of question on a bottom.
I'd like to implement custom authentication for SignalR clients. In my case this is java clients (Android). Not web browsers. There is no Forms authentication, there is no Windows authentication. Those are plain vanilla http clients using java library.
So, let's say client when connects to HUB passes custom header. I need to somehow authenticate user based on this header. Documentation here mentions that it is possible but doesn't give any details on how to implement it.
Here is my code from Android side:
hubConnection = new HubConnection("http://192.168.1.116/dbg", "", true, new NullLogger());
hubConnection.getHeaders().put("SRUserId", userId);
hubConnection.getHeaders().put("Authorization", userId);
final HubProxy hubProxy = hubConnection.createHubProxy("SignalRHub");
hubProxy.subscribe(this);
// Work with long polling connections only. Don't deal with server sockets and we
// don't have WebSockets installed
SignalRFuture<Void> awaitConnection = hubConnection.start(new LongPollingTransport(new NullLogger()));
try
{
awaitConnection.get();
Log.d(LOG_TAG, "------ CONNECTED to SignalR -- " + hubConnection.getConnectionId());
}
catch (Exception e)
{
LogData.e(LOG_TAG, e, LogData.Priority.High);
}
P.S. Original question below was my desire to "simplify" matter. Because I get access to headers in OnConnected callback. I thought there is easy way to drop connection right there..
Using Signal R with custom authentication mechanism. I simply check if connecting client has certain header passed in with connection request.
Question is - how do I DECLINE or NOT connect users who don't pass my check? Documentation here doesn't really explain such scenario. There is mentioning of using certificates/headers - but no samples on how to process it on server. I don't use Forms or windows authentication. My users - android java devices.
Here is code from my Hub where I want to reject connection..
public class SignalRHub : Hub
{
private const string UserIdHeader = "SRUserId";
private readonly static SignalRInMemoryUserMapping Connections = new SignalRInMemoryUserMapping();
public override Task OnConnected()
{
if (string.IsNullOrEmpty(Context.Headers[UserIdHeader]))
{
// TODO: Somehow make sure SignalR DOES NOT connect this user!
return Task.FromResult(0);
}
Connections.Add(Context.Headers[UserIdHeader], Context.ConnectionId);
Debug.WriteLine("Client {0}-{1} - {2}", Context.Headers[UserIdHeader], Context.ConnectionId, "CONNECTED");
return base.OnConnected();
}
So I just created a custom Authorization Attribute and overrode the AuthorizeHubConnection method to get access to the request and implemented the logic that you were trying to do with the Header and it appears to be working.
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace SignalR.Web.Authorization
{
public class HeadersAuthAttribute : AuthorizeAttribute
{
private const string UserIdHeader = "SRUserId";
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
if (string.IsNullOrEmpty(request.Headers[UserIdHeader]))
{
return false;
}
return true;
}
}
}
Hub
[HeadersAuth]
[HubName("messagingHub")]
public class MessagingHub : Hub
{
}
Which yields this in the console (if the picture doesn't show up, it's a [Failed to load resource: the server responded with a status of 401 (Unauthorized)]):
In fact, accepted answer is wrong. Authorization attribute, surprisingly, shall be used for authorization (that is, you should use it for checking whether requesting authenticated user is authorized to perform a desired action).
Also, since you using incorrect mechanics, you don't have HttpContext.Current.User.Identity set. So, you have no clear way to pass user info to your business / authorization logic.
And third, doing that you won't be able to use Clients.User() method to send message to specific user, since SignalR will be not able to map between users and connections.
The correct way is to plug in into OWIN authentication pipeline. Here is an excellent article explaining and demonstrating in detail how to implement custom authentication to be used in OWIN.
I not going to copy-paste it here, just follow it and make sure you implement all required parts:
Options
Handler
Middleware
After you have these, register them into OWIN:
app.Map("/signalr", map =>
{
map.UseYourCustomAuthentication();
var hubConfiguration = new HubConfiguration
{
Resolver = GlobalHost.DependencyResolver,
};
map.RunSignalR(hubConfiguration);
});

Wpf client and asp.net web app signalr Hub , save users logged in from wpf client in signalr server

I am creating SignalR hub in asp.net web app. My wpf application is a client for this SignalR hub
I have login facility in my wpf application , I want to store this users on hub created in asp.net , so that I can send information to specific user.
I want to store two properties UserName and UserToken , the list of these properties in hub , How I can send this properties information to hub
I tried using Client.Caller but its not getting any value in connected or disconnected event on hub
You can access the user in your hub methods: there is a User instance property. For the token, I will assume it is in the request headers. You can access that in the hub Context.Request.Headers property. You don't need to store users to send data to them. Somewhere (not in a hub inheritor) you will have a line like this:
private static readonly Lazy<IHubContext> _hubCtx = new Lazy<IHubContext>(() => GlobalHost.ConnectionManager.GetHubContext<MyHub>());
protected virtual Task SendReportToAll(Report r)
{
return _hubCtx.Value.Clients.All.EventToTriggerOnClient(r);
}
protected virtual Task SendReportToUser(Report r, string username)
{
return _hubCtx.Value.Clients.User(username).EventToTriggerOnClient(r);
}

SignalR .NET Client not receiving messages

I have a .NET console application that I am practicing signalR with.
var hubConnection = new HubConnection("http://URL/signalr/");
var hub = hubConnection.CreateHubProxy("Hub");
hub.StateChanged += change =>
{
Console.WriteLine(change.NewState);
};
hub.Received += s =>
{
Console.WriteLine(s);
};
hub.On<string, string>("processMessage", (group, message) =>
{
Console.WriteLine(message);
});
await hubConnection.Start();
await hub.Invoke<string>("Subscribe", "New group");
I see the state changing from Connecting to Connected but I am not getting a "Received" event on the client when the server sends a message. The server is sending a group message as soon as the client subscribes and I can see the message being sent with the correct "New group" groupname, however I never receive the message on the client. I also do not receive the processMessage event when the server uses that method.
Server Code
private void CallBack(string group, string message)
{
Clients.Group(group).processMessage(group, message);
}
The other method on the server is Subscribe which just sets my inner server to use the CallBack method when it has data available to send to the client.
Edit
This works in Javascript it just doesn't seem to work in the .NET client.
Without full serverside code it's hard to say but I think this part is wrong
hubConnection.CreateHubProxy("Hub");
as argument you need the name of your hubclass on serverside. For example
hubConnection.CreateHubProxy("MyHub");
To get more informations on clientside why it fails you can temporary add the following to your HubConnection
hubConnection.TraceLevel = TraceLevels.All;
hubConnection.TraceWriter = Console.Out;
After adding this you will get further debuging informations in your output section in VS

SignalR Security

I am new to SignalR but I was curious about how secure it is.
For example, I create the following method to send a message to all users like so:
public class NotificationHub : Hub
{
public void Send(string message)
{
Clients.All.broadcastMessage(message);
}
}
SignalR generates the following method in a js file (hubs):
proxies.notificationHub.server = {
send: function (message) {
return proxies.notificationHub.invoke.apply(proxies.notificationHub, $.merge(["Send"], $.makeArray(arguments)));
}
};
So, couldn't any user in the world just copy and paste this into their console and send a message of their choice to all of my users without my say-so?
var notifications = $.connection.notificationHub;
notifications.server.send("Your site has been hacked!");
I just tried this and it works - so, how can I prevent my users from sending unauthorized messages from the client side?
It's an HTTP endpoint like any other. If you want to restrict access to it you need to authenticate users and authorize their actions. You authenticate using standard web auth methods (forms auth, cookies, Windows auth, etc.) and you can authorize in code using SignalR constructs (like the Authorize attribute you point out) or with your own code.
This is all documented: http://www.asp.net/signalr/overview/signalr-20/security/introduction-to-security

Categories