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);
});
Related
I have Angular SPA ASP.NET Core app with Identity (IdentityServer4). I use SignalR to push real-time messages to clients.
However I have to "broadcast" messages. All clients receive same messages regardless of what they require and then they figure out in Typescript - do they need this message or not.
What I want is to be able to decide which SignalR client should receive message and what content - it will make messages shorter and cut out processing time on clients completely.
I see there is hub.Client.User(userId) method - thats what I need.. However it appears that the Identity user ID is not known to SignalR.
If I override public override Task OnConnectedAsync() - context inside doesnt have any useful information eg user/principals/claims - are empty.
How can I find out which IdentityServer4 user is connecting to the hub?
EDIT1 suggested implementing IUserIdProvider doesnt work - all xs are null.
https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0#use-claims-to-customize-identity-handling
public string GetUserId(HubConnectionContext connection)
{
var x1 = connection.User?.FindFirst(ClaimTypes.Email)?.Value;
var x2 = connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var x3 = connection.User?.FindFirst(ClaimTypes.Name)?.Value;
...
EDIT2 implemented "Identity Server JWT authentication" from https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0 - doesnt work either - accessToken is empty in PostConfigure
You need to implement IUserIdProvider and register it in the services collection.
Check this question - How to user IUserIdProvider in .NET Core?
There is an obvious solution to it. Here is the sample one can use after creating an asp.net core angular app with identity.
Note that in this particular scenario (Angular with ASP.NET Core with Identity) you do NOT need to implement anything else, in contrary to multiple suggestions from people mis-reading the doc: https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0
Client side:
import { AuthorizeService } from '../../api-authorization/authorize.service';
. . .
constructor(. . . , authsrv: AuthorizeService) {
this.hub = new HubConnectionBuilder()
.withUrl("/newshub", { accessTokenFactory: () => authsrv.getAccessToken().toPromise() })
.build();
Server side:
[Authorize]
public class NewsHub : Hub
{
public static readonly SortedDictionary<string, HubAuthItem> Connected = new SortedDictionary<string, HubAuthItem>();
public override Task OnConnectedAsync()
{
NewsHub.Connected.Add(Context.ConnectionId, new HubAuthItem
{
ConnectionId = Context.ConnectionId,
UserId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value
});
return base.OnConnectedAsync();
}
}
Use it like this:
if(NewsHub.Connected.Count != 0)
foreach (var cnn in NewsHub.Connected.Values.Where(i => !string.IsNullOrEmpty(i.UserId)))
if(CanSendMessage(cnn.UserId)
hub.Clients.Users(cnn.UserId).SendAsync("servermessage", "message text");
It is transpired that User data is empty in SignalR server context because authorization doesnt work as I expected it to. To implement SignalR authorization with Identity Server seems to be a big deal and is a security risk as it will impact the whole app - you essentially need to manually override huge amount of code which already is done by Identity Server just to satisfy SignalR case.
So I came up with a workaround, see my answer to myself here:
SignalR authorization not working out of the box in asp.net core angular SPA with Identity Server
EDIT: I missed an obvious solution - see the other answer. This is still valid workaround though, so I am going to let it hang here.
I am using SignalR with my WebApi application and that works fine: the client connects to the hub using WebSocket transport and the user is authenticated with some custom middleware authentication.
I am trying to connect to this hub from another backend application (.NET client) and to do this I make a hub connection and create a hub proxy, then invoke the hub method:
string auth = "someEncryptedValue"
HubConnection hubConn = new HubConnection("myUrl");
hubConn.Headers.Add("myauthtoken", auth);
IHubProxy proxy = hubConn.CreateHubProxy("hubName");
Task t = Task.Run(() => hubConn.Start(new LongPollingTransport()));
t.WaitAndUnwrap // An extension method
hubProxy.Invoke("SendMessage", message);
The exception is thrown when t.WaitAndUnwrap() is called (the extension method is not the issue). I had this incoming principal is null issue before I added the http header token (which, of course, is not actually the literal string "someEncryptedValue"). So I added that here and then I added a custom authorization class for the hub back in my other application:
public class HeadersAuthAttribute : AuthorizeAttribute
{
public override bool AuthroizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
string auth = request.Headers["myauthtoken"];
if (auth == "someEncryptedValue") //again, this is actually more complex than shown here
{
return true;
}
else
{
// Need to check if the incoming principal is authenticated in case
// the connection to the hub is being made the normal way through the
// WebApi instead of the proxy using http headers
return request.User?.Identity?.IsAuthenticated ?? false;
}
return false;
}
}
And of course my hub has the headers attribute:
[HeadersAuth]
public class MyHub : Hub
{
// Hub logic
}
However, after running this I still get the incoming principal is null error. Then I read that you cannot use custom http headers with WebSockets, which is why I put new LongPollingTransport() in the hubConn.Start call above. But that didn't seem to change anything, at least not the error I get.
Does anyone know what can be going on? It'd be nice if I could debug the code with the actual hub and authorization so I can see what's going on when the hubConn.Start call is made. Is there a way I can check the http header is set correctly and fetched correctly? If the error is about the incoming principal being null, is authentication even the issue? Could it be another part of the code where it's trying to find the user? I'm not sure what to do about that since this hub connection is being made from a .NET client. Also, I know the HeadersAuthAttribute class is being called and is used correctly when connecting to the hub normally using WebSockets since it goes into the else case where it checks that the IIdentity is authenticated.
Just to add a bit more of what I've tried:
I made the auth string token purposefully wrong to see if I get a different error, but I still get incoming principal is null.
I realized that my OnConnected() override method for my hub calls a method that tries to use the incoming principal with this.Context.User without checking if it's null. I removed that and anything else that tries to use the incoming principal in the hub, but unfortunately it still gives the same error.
I figured out what was wrong. It wasn't to do with the hub authentication, as I suspected as I was finishing this post. What was happening was my Startup file makes the call app.ConfigureAuth, but this eventually leads to some custom authorization in the OWIN middleware pipeline that checks if the incoming principal is null. I've taken that out for SignalR connections.
It should also be noted that after I got that working I couldn't make hub method calls from the proxy because it wasn't authorized to do so. To fix that I added
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
to the HeadersAuthAttribute class, which I got from here.
I have a small project which contains a windows forms signalr client and a windows forms server. Clients can connect to server. Now, I want to implement a login system. I read some blogposts, articles and questions about this, but I didn't found a way to do it. I would like to use the signalr authentication system so I can use attributes such as [Authorize] because it's already there.
To use this, I need to validate the username and password. Can the client sends the credentials in header like this
Connection = new HubConnection(BaseEngine.ServerURI);
Connection.Headers.Add("Username", username);
Connection.Headers.Add("Password", password);
HubProxy = Connection.CreateHubProxy("ChatHub");
await Connection.Start();
and the server should validate those credentials somehow in a method and throw an exception if are not valid?
I've tried to use the builtin system, but no luck. I couldn't obtain the Context.User in OnConnected method. As a workaround, I've tried to send the username and password in header and validate them, but the OnConnected method cannot throw errors to client. I am sure the client has to have an auth cookie, but I really don't know how to add it.
Thank you!
It is never a good idea to send password to the server this way , it is better to send a token that the server can validate .
also SignalR has some authentication features read more about this here
I have found something like a workaround: first of all, I implemented an attribute, derived from AuthorizeAttribute used by SignalR. This implementation overrides the AuthorizeHubMethodInvocation method witch is called when a method that is decorated with this attribute is called. So, in this method, I'm checking that an Authorization Token is present in the header of the request and validate the information. The client has to add this header to connect to the server. It's the easiest method I have found so far, but it's still an workaround.
Implementation, server:
[AttributeUsage(AttributeTargets.Method)]
internal class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubMethodInvocation(Microsoft.AspNet.SignalR.Hubs.IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
string token = hubIncomingInvokerContext.Hub.Context.Headers["AuthenticationToken"];
if (string.IsNullOrEmpty(token))
return false;
else
{
string decryptedValue = Encryptor.Decrypt(token, Encryptor.Password);
string[] values = decryptedValue.Split(';');
string userName = values[0],
deviceId = values[1];
bool b = ...check if it's ok...
return b;
}
}
}
Implementation, client:
ComEngine.Connection = new HubConnection(BaseEngine.ServerURI);
ComEngine.Connection.Headers.Add("AuthenticationToken", Encryptor.Encrypt(string.Format("{0};{1};{2}", BaseEngine.UserName, BaseEngine.DeviceId, BaseEngine.Password), Encryptor.Password));
try
{
await Connection.Start();
}
catch (Exception ex)
{
...
}
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
Using SignalR, I believe I should be able to send messages to specific connected users by using UserID Provider
Does anyone have an example of how this would be implemented? I've searched and searched and can not find any examples. I would need to target a javascript client.
The use case is, users to my site will have an account. They may be logged in from multiple devices / browsers. When some event happens, I will want to send them a message.
I have not looked into SignalR 2.0 but I think this is an extension of what the previous versions of SignalR used to have. When you connect to the hub you can decorate it with an Authorize attribute
[HubName("myhub")]
[Authorize]
public class MyHub1 : Hub
{
public override System.Threading.Tasks.Task OnConnected()
{
var identity = Thread.CurrentPrincipal.Identity;
var request = Context.Request;
Clients.Client(Context.ConnectionId).sayhello("Hello " + identity.Name);
return base.OnConnected();
}
}
As you can see you are able to access the Identity of the user accessing the Hub. I believe the new capability would be nothing more than an extension of this. Since the connection is always kept alive between the client and the hub you will always have the principal identity which will give you the UserId.
I believe this can help you: (linked from here)
A specific user, identified by userId.
Clients.User(userid).addContosoChatMessageToPage(name, message);
The userId can be determined using the IUserId interface:
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
The default implementation of IUserIdProvider is PrincipalUserIdProvider. To use this default implementation, first register it in GlobalHost when the application starts up:
var idProvider = new PrincipalUserIdProvider();
GlobalHost.DependencyResolver.Register (typeof(IUserIdProvider), () => idProvider);
The user name can then be determined by passing in the Request object from the client.