SignalR Security - c#

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

Related

Error thrown when calling HubConnection.Start: StatusCode: 401, ReasonPhrase: 'Incoming principal is null'

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.

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);
});

SignalR - authenticate windows forms user on windows forms server

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)
{
...
}

Basic authentication on a remote server

I need some help with ASMX web-services.
Let's suppose I have a ServerService which provides some data. Let's suppose it has a method GetRandomInteger which returns a random integer (obviously). It implements a custom basic authentication using IHttpModule.
public class BasicAuthHttpModule : IHttpModule
{
private UserRepository _userRepository;
public void Dispose()
{
}
public void Init(HttpApplication application)
{
_userRepository = new UserRepository();
application.AuthenticateRequest += OnAuthenticateRequest;
application.EndRequest += OnEndRequest;
}
public void OnAuthenticateRequest(object source, EventArgs e)
{
var app = (HttpApplication)source;
string authHeader = app.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authHeader))
{
// Here I successfully get credentials from header
if (_userRepository.ValidateUser(username, password)) return;
// Return 401 and CompleteRequest
}
else
{
// Return 401 and End
}
}
public void OnEndRequest(object source, EventArgs eventArgs)
{
if (HttpContext.Current.Response.StatusCode == 401)
{
// Return 401 and require new authorization
}
}
Fortunately, it works. Now I can successfully open Service.asmx file, get basic authentication window and get access to it's GetRandomInteger method after successful authentication.
Now I have an ASP.NET MVC 4 application called ClientService. It must provide user interface with convenient and appropriate access to methods of ServerService. Now it has default controllers like Account and Home, default views etc.
I need this ClientService to authenticate on a ServerService. I mean there will be a Home/Index page with button "Login". I enter login and password there and ClientService tries to authenticate at ServerService. It returns error on fail or authenticates on success providing access to some Home/RandomInt page which will show the integer requested from ServerService. What is the best and the easiest way to do this?
How to implement registration on a ServerService? There is no AllowAnonymous attribute or something at ASMX, so I can't register user because he doesn't have access to any of methods due to 401 error.
Thank you in advance.
P.S. No. I can't use WCF or something else. I need to implement an ASMX web-service.
Update 1: OK, I have learned something new from here
http://www.aspsnippets.com/Articles/How-to-add-reference-of-Web-Service-ASMX-in-ASPNet-using-Visual-Studio.aspx
There is an old-style thing like "Web reference" and it's not an "Service reference". I have added this Web reference to my project and now I can call some methods from this ASMX page in this way:
try
{
ServerService svc = new ServerService();
svc.Credentials = new NetworkCredential("user", "password");
int a = svc.GetRandomInteger();
} catch (WebException e) {
// Auth failed
}
However, I don't understand how to link it with ASP.NET MVC ClientService authentication. So, both questions are still open. Hopefully, I will understand it or you will help me.
Here is a documentation for adding a Web reference to an ASMX Service.
http://www.aspsnippets.com/Articles/How-to-add-reference-of-Web-Service-ASMX-in-ASPNet-using-Visual-Studio.aspx
Using this information I can easily make requests to a web service.
The only thing I left to do on the moment of question update is to create a custom authentication.
When user logins, the client sends a request to a service. In case of successful basic authentication, it creates proper FormsAuthentication cookie ticket for a user. User logs in.
On each request to a service, the client extracts login from FormsAuthentication cookie and his password from server cache and uses them to authenticate on a service. In case of basic auth failure (it can only occur if user's password has been changed on the service side) the cookie is cleared and session is aborted.
Registration is implemented using another one ASMX service which is not using basic auth but is anonymous (because registration is supposed to be anonymous method).
That's it. Finally, I have found a proper solution :)

SignalR - Send message to user using UserID Provider

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.

Categories