13

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();
    }
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
katit
  • 17,375
  • 35
  • 128
  • 256
  • So, the answer is "impossible"? – katit Aug 01 '16 at 16:05
  • On the client you could hit an endpoint where you try to validate if the user should be able to connect or not, and run your hub connection code based on that logic. Does that sound feasible to you? – Mark C. Aug 01 '16 at 16:07
  • 1
    I'm not sure why _client_ should even be considered. I want to make sure SignalR does not populate internal connections if I tell it not to. Client can be anything, maybe DOS attack with proper request structure. From what I see - there is no way to tell in my code to just drop connections if I don't see what I need to see in a header? Telling client to disconnect via Hub code is not logical, client may not even support this – katit Aug 01 '16 at 16:10
  • I guess I'm looking for a way to Authenticate user using my custom code based on header contents – katit Aug 01 '16 at 16:11
  • No. I want to refuse clients from connecting. I don't want to call client-side code because they may not even support it (automatic bot for example). I'm OK if there is way to check user based on header so OnConnected does not even execute – katit Aug 01 '16 at 16:14
  • How is a user supposed to get a `UserIdHeader` before they connect for the first time? – krillgar Aug 01 '16 at 17:26
  • User _sends_ this header, not get's it from server. On server I need to authenticate user based on this header from user. If it's a bot/anonymous user - header will be wrong and I don't want to connect them – katit Aug 01 '16 at 17:28
  • Have you read [this article](http://www.asp.net/signalr/overview/security/hub-authorization) It explains some custom auth approaches? – Mark C. Aug 01 '16 at 18:07
  • Yes I did. I want to use header to pass auth info. There is no samples on this. Read last comment to this article and comment number 6 from a bottom. Basically, I need a sample explaining WHERE in SignarR pipeline I need to run my custom Authentication routine – katit Aug 01 '16 at 18:11
  • Can you update your question and explain how you expect this all to work? – Mark C. Aug 01 '16 at 18:28
  • Done. I changed actual question. – katit Aug 01 '16 at 18:35
  • As you've probably seen discontecting users is not possible yet :https://github.com/SignalR/SignalR/issues/2101 .That said , I tried throwing a exception inside OnConnected before calling base.OnConnected and seems to be working...Don't know though if this is a good idea... – George Vovos Aug 01 '16 at 18:55
  • @GeorgeVovos Not sure if that github issue related.. Maybe don't need to stop connection? Maybe custom Authentication should do? Let's say I got header with UN/PW and checked it? Just not sure where and how this code should be implemented – katit Aug 01 '16 at 18:59

2 Answers2

8

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)]):

enter image description here

Mark C.
  • 6,332
  • 4
  • 35
  • 71
  • Mark, looks good, but I started to test and for some reason header is not there. I do see header in OnConnected, but in AuthorizeHubConnection there is only 4 standard headers – katit Aug 01 '16 at 19:59
  • Can you update your question and show how you're placing your header onto the request? – Mark C. Aug 01 '16 at 20:02
  • Done. I also tried to add standard "Authorization" header and it's not there. It is there in "OnConnected" method. – katit Aug 01 '16 at 20:06
  • It looks like this may not actually be possible for the websocket protocol. [Source](http://stackoverflow.com/a/16808219/2679750). Could possibly add it to the Query String (which you have access to in `IRequest` as well) – Mark C. Aug 01 '16 at 20:07
  • It's long-polling, not websocket – katit Aug 01 '16 at 20:08
  • I see that now. Does [this](https://github.com/SignalR/java-client/issues/22) help? – Mark C. Aug 01 '16 at 20:13
  • I guess it will help :) I really didn't want to go that route. That java library is half-broken and I try to use minimum of it. But why is header not there? – katit Aug 01 '16 at 20:28
  • You could debug the source code around this line `HttpRequest realRequest = createRealRequest(request);` and see if your headers are there. If they aren't, I would just trust the developer and implement the interface he refers to. I'm not a java developer so I'm not that helpful for that particular issue. – Mark C. Aug 01 '16 at 20:35
  • Mark, this refers to "ndicates if the default SignalR URL should be used" - so it should be true. I'm working on this Authenication thing from Java side, will see what it looks like.. – katit Aug 01 '16 at 20:39
  • Yeah, that's why I deleted my comment. – Mark C. Aug 01 '16 at 20:40
  • Instead of a header, can you use a query string parameter? And then check for that? After looking, it seems like that should work. – Mark C. Aug 01 '16 at 20:45
  • Query string works. But other side effect noted. AuthorizeHubConnection called every time client reconnects. Which happens every 2 minutes with long polling. Don't really want to check everything every 2 minutes. OnConnected called just once and this is ideal spot.. – katit Aug 01 '16 at 21:05
  • You can throw the connection Ids you want to refuse into a static HashSet or Redis or something. I'm afraid I'm at the end of where I can be helpful here. The Java library looks simple enough to read through, I just don't have a Java IDE to play around with. The reason there are no headers that you set on the first request (through the Auth attribute) is that it simply uses a URL and query string to [initiate the SignalR connection](https://github.com/SignalR/java-client/blob/master/signalr-client-sdk/src/main/java/microsoft/aspnet/signalr/client/transport/HttpClientTransport.java#L71) it seem – Mark C. Aug 01 '16 at 21:06
  • Once you are connected, all you can do is stop that Client's connection. You've stated that you didn't want to do that, but that is what I tried to explain in my initial comments on your question. Let me say again, if you are in the `OnConnected` method, you've already established a `ConnectionId` and have given the Client access to the Hub. – Mark C. Aug 01 '16 at 21:09
  • Yes, understand all that. And thanks a lot for help! Query string will work to pass data (already works). Just need to avoid re-checks in case it called on already established connection. But I _think_ I know what to do now. I already heave in-memory user/connection hash. I can just re-check if user connected before doing Authorization against database – katit Aug 01 '16 at 21:11
  • No problem. Is my answer useful at all? If not I will delete it. – Mark C. Aug 01 '16 at 21:18
  • It is useful. Keep it :) I'm not going to get better one I afraid. And you helped me through this – katit Aug 01 '16 at 21:19
  • Sounds good. Glad you could find a solution, even though it isn't optimal or the 'neatest' way. – Mark C. Aug 01 '16 at 21:20
6

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);
});
Illidan
  • 4,047
  • 3
  • 39
  • 49
  • The critical part of the article is at https://dotnetcodr.com/2015/11/26/wiring-up-a-custom-authentication-method-with-owin-in-web-api-part-4-putting-the-components-to-work/ – CRice Oct 30 '18 at 04:32
  • Yes it is better to separate authentication from authorization - this is a great answer. – CRice Oct 30 '18 at 04:34