Search code examples
signalrsignalr-hubasp.net-core-signalr

Passing JWT Token as QueryString to SignalR Hub


Trying to follow the suggestions in the link below to pass a JWT token to my SignalR hub but so far it's not working. In particular, see David Fowler's suggestion on July 22, 2017. https://github.com/aspnet/SignalR/issues/130

My frontend is React so I'm simply adding the token to the querystring as follows where _token has my JWT token value:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/myhub?AUTHORIZATION=" + _token)
    .configureLogging(signalR.LogLevel.Information)
    .build();

In the ConfigureServices() method of my Startup.cs, I have the following configuration for Jwt tokens:

services.AddAuthentication(options => {
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            })
              .AddJwtBearer(jwtOptions => {
                  jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
                  jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
                  jwtOptions.Events = new JwtBearerEvents
                  {
                      OnMessageReceived = context =>
                      {
                          if(context.HttpContext.WebSockets.IsWebSocketRequest)
                              context.Token = context.Request.Query["AUTHORIZATION"];

                          return Task.CompletedTask;
                      }
                  };
              });

And this is what my Hub looks like:

[Authorize]
public class MyHub : Hub
{
   private IBackendService _backendService;
   public MyHub(IBackendService backendService)
   {
       _backendService = backendService;
   }

   public async Task SendMessage(string message)
   {
       // Regular SignalR stuff
       // SignalR will now send the message to all connected users...
   }
}

Basically, I'm getting the 401 Unauthorized error.

I put a break point where I check to see if the request is a web sockets request but I'm not hitting it. Looks like something in the pipeline is determining that the user is not authenticated.

What am I doing wrong in my code?


Solution

  • You can solve this by using custom middleware to handle grabbing the authentication token from the query string.

    public class SignalRQueryStringAuthMiddleware
    {
        private readonly RequestDelegate _next;
    
        public SignalRQueryStringAuthMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        // Convert incomming qs auth token to a Authorization header so the rest of the chain
        // can authorize the request correctly
        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Headers["Connection"] == "Upgrade" &&
                context.Request.Query.TryGetValue("authToken", out var token))
            {
                context.Request.Headers.Add("Authorization", "Bearer " + token.First());
            }
             await _next.Invoke(context);
        }
    }
    
    public static class SignalRQueryStringAuthExtensions
    {
        public static IApplicationBuilder UseSignalRQueryStringAuth(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<SignalRQueryStringAuthMiddleware>();
        }
    }
    

    This will try to get the query string value "authToken" and it will set the heads so you can leverage your authentication middleware. You need to call this before the authentication middleware in the pipeline like so:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        //...
    
        app.UseSignalRQueryStringAuth();
        app.UseAuthentication();
    
        //...
    }
    

    EDIT

    on a side note you should only append the token if the user is logged in:

    if (accessToken) {
        hubUrl += '?authToken' +'=' + accessToken;
    }
    
    this._hubConnection = new HubConnectionBuilder()
                                    .withUrl(hubUrl)
                                    .build();