25

I've been building a web service that, up to now, consisted entirely of ASP.NET Web API calls. I now need to add SignalR into the mix but am wondering how I can keep the security aspect consistent.

I'm currently using OAuth bearer tokens (with OWIN authentication), so my client will basically have an access_token that I'd normally simply add to the headers for any web API calls. However, how do I do the same (or similar) with SignalR? Would I authenticate when I create the connection or on each call? And, most importantly, how would I specify the access token in the first place?

Thanks.

Barguast
  • 5,926
  • 9
  • 43
  • 73
  • JavaScript WebSocket API does not allow to set request headers. Your best option would be to set access_token on the request url query string. – Gustavo Armenta Jan 21 '14 at 20:57
  • Thanks. I can do that, but how would I configure the server side to read the query string and determine and set the identity for my hubs? – Barguast Jan 21 '14 at 22:05
  • @Barguast you could create a HubPipelineModule that sets the user identity for valid tokens or denies access for invalid tokens. The two methods you would need to override for this to work are `OnBeforeConnect` and `OnBeforeIncoming`. http://msdn.microsoft.com/en-us/library/microsoft.aspnet.signalr.hubs.hubpipelinemodule(v=vs.118).aspx – halter73 Jan 22 '14 at 00:47
  • Thanks. Hopefully more has been written on this somewhere? I can see how the module can intercept the request, and probably how the token can be decrypted (although it'd be messy), but I don't see how I can see the User property on the context. I had assumed the work I had to do would be configuring OWIN rather than SignalR. – Barguast Jan 22 '14 at 09:29
  • I think I've made some progress. I've followed the answer to this question: http://stackoverflow.com/questions/20585872/get-iprincipal-from-oauth-bearer-token-in-owin, and am now able to provide the access token in the query string and get the identity from it on the server. My WebAPI controllers are all now getting the principal from either Authorization header, or the query string. However, the User property in SignalR is still the default 'not authenticated' value! How can I set the value of which this property should be on the OWIN configuration? – Barguast Jan 22 '14 at 19:09
  • You have two options: 1. Write an OWIN Middleware that executes at the begininning of the pipeline to read access token from request query string and insert it as a request header 2. Read Katana documentation or tests to find out how to make Katana to read the access token from the query string – Gustavo Armenta Jan 23 '14 at 18:06
  • Well, a third option is simply to authenticate with bearer tokens and pass a cookie to signalr. There is a sample here (https://github.com/gustavo-armenta/BearerTokenAuthenticationSample) – Gustavo Armenta Jan 23 '14 at 18:30

2 Answers2

6

(this is so late :D but maybe useful for someone) you can add accessTokenFactory to hubConnection in client side, and then check it in your backend(asp.net core 3.1).

from BackEnd you must override JwtBearerEvents and check access_token inside OnMessageReceived, also your hub endpoint path

services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(options =>
        {
            // other options here ... //

            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var accessToken = context.Request.Query["access_token"];
                    var path = context.HttpContext.Request.Path;
                    if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/ConnectionHub"))) // for me my hub endpoint is ConnectionHub
                    {
                        context.Token = accessToken;
                    }
                    return Task.CompletedTask;
                }
            };
        });

from client side, add your accessToken options (TypeScript):

public async startConnection(): Promise<void> {
    this.hubConnection = new signalR.HubConnectionBuilder()
        .withUrl('https://localhost:5001/ConnectionHub', { // ConnectionHub is hub endpoint
        accessTokenFactory: () => this.getAccessToken(),
        skipNegotiation: true,
        transport: signalR.HttpTransportType.WebSockets
    }).build();

    await this.hubConnection.start();
    // ....
}

getAccessToken(): string {
    return 'your token' ;   }

microsoft said: Individual hub methods can have the [Authorize] attribute applied as well

[Authorize]
public class ChatHub : Hub
{
    public async Task Send(string message)
    {
        // ... send a message to all users ...
    }

    [Authorize("Administrators")]
    public void BanUser(string userName)
    {
        // ... ban a user from the chat room (something only Administrators can do) ...
    }
}

and finaly you can read and check claims or identity attributes inside any of hub's method:

public override async Task OnConnectedAsync()
    {
        // Get UserID. Assumed the user is logged before connecting to chat and userid is saved in session.
        string userID = Context.User.Identity.Name;

        // Get ChatHistory and call the client function. See below
        await GetHistoryAsync(userID);

        await base.OnConnectedAsync();
    }
Mohammad Reza Mrg
  • 1,552
  • 15
  • 30
  • I have the backend StartUp.cs set up as you written however I'm not clear on how to access the token in the `getAccessToken()` method, specifically, `UserData` ? – eddyizm Apr 30 '21 at 21:19
  • UserData was my own class variable.You can just send your jwt from where you store it befor.For test try to return your jwt ditrctly. – Mohammad Reza Mrg Aug 26 '21 at 09:47
  • 1
    This approach is inherently insecure as any request logging will end up logging you query strings (with the access token). – lukejkw Jan 13 '23 at 08:15
  • I agree with you, But for putting token in header I think we have to use HTTP instead – Mohammad Reza Mrg Jan 14 '23 at 09:09
1

I have written a sample that has helped out a few people already with SignalR authentication. I hope that it may help you too.

https://github.com/louislewis2/AngularJSAuthentication

Barguast, sorry for the brevity of my response. Going through the link you might understand that there are too many moving parts for me to accurately write an explanation of the most important parts that you should pay attention too. The sample code contains three project, of which you will be concerned with only the .Api and the .Web projects.

Let me know if there is any part that you need further clarification or guidance on, and I will happily help you out as all the other people who have encountered a need for the same example, which is why I was asked to build the example out in the first place.

Louis Lewis
  • 1,298
  • 10
  • 25
  • I think the comment (that was auto-generated by the LowQualityPosts-Review-Queue) says enough, but please read the guidelines for SO – msrd0 Nov 02 '14 at 20:17
  • Thanks. It looks like you got around it in the same way I did - adding the access token to the query string, and modifying the service to look there for the token. It has been working well for me, and I've yet to see any other way of doing it. – Barguast Nov 03 '14 at 14:55
  • Fantastic to hear the you came right. There is another way of doing it, where you can place the token in the headers, however there is one big issue going with that route. As you know signalr supports websockets and to me that was important. websockets do not support adding a custom headers, so going this route, you would have to forget about using websockets as a transport. other than that, there is no other way that I have found. – Louis Lewis Nov 03 '14 at 18:31