'SignalR hub with Bearer authentication

I have a problem. I have in my API JWT Bearer authentication. I try to use SignalR hub with authentication but it doesn't work for me. I think I tried everything.

I have something like this:

.AddJwtBearer(conf =>
                {
                    conf.RequireHttpsMetadata = false;
                    conf.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                        ValidateIssuer = false,
                        ValidateAudience = false
                    };
                    conf.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = context =>
                        {
                            // THIS DOESN'T WORK - empty string
                            //var accessToken = context.Request.Query["access_token"];
                            var accessToken2 = context.Request.Headers["Authorization"];
                            // If the request is for our hub...
                            var path = context.HttpContext.Request.Path;
                            if (!string.IsNullOrEmpty(accessToken2) &&       
                            (path.StartsWithSegments("/DebateHub")))
                            {
                                // Read the token out of the query string
                                context.Token = accessToken2;     
                            }
                           // return Task.CompletedTask;
                           return Task.FromResult<object>(null);
                        }
                    };
                });

Register hub:

 app.UseEndpoints(endpoints =>
        {
            endpoints.MapAreaControllerRoute(
                name: "AreaAdmin",
                areaName: "Admin",
                pattern: "api/admin/{controller}/{action}");

            endpoints.MapAreaControllerRoute(
                name: "AreaMobile",
                areaName: "Mobile",
                pattern: "api/mobile/{controller}/{action}");

            endpoints.MapControllers();
            endpoints.MapHub<DebateHub>("/DebateHub");
            endpoints.MapHub<OnlineCountHub>("/onlinecount");
        });

Hub code:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class DebateHub : Microsoft.AspNetCore.SignalR.Hub
{      
    public override Task OnConnectedAsync()
    {
        string name = Context.User.Identity.Name;      
       
        Groups.AddToGroupAsync(Context.ConnectionId, name);
       
        return base.OnConnectedAsync();
    }
}

Client example:

var uri = "https://localhost:44275/DebateHub";         
        
var connection = new HubConnectionBuilder()
                     .WithUrl(uri,options =>
                          {                   
                               options.AccessTokenProvider = () => Task.FromResult("some_token");
                          })
                     .Build(); 
connection.StartAsync().Wait();

It doesn't work. I still have unauthorized when I try to connect to my DebateHub. All other controllers work with my authentication ok.

What am I doing wrong?



Solution 1:[1]

I'm not sure but I think that you should use cookies to authorize to hub.

Look here

Solution 2:[2]

You must uncomment this part of your code;

//var accessToken = context.Request.Query["access_token"];

when hub connection request comes to the server it only sets the token in 'context.Request.Query', as microsoft docs states not in context.Request.Headers["Authorization"].

The query string is used on browsers when connecting with WebSockets and Server-Sent Events due to browser API limitations.

Confirm in chrome network tab request headers to see where it is being sent.

Alternatively you can use this middleware in startup configure method which dose same thing by taking the token from query and setting it where expected to be.

 app.Use(async (context, next) =>
        {
            var accessToken = context.Request.Query["access_token"];
            if (!string.IsNullOrEmpty(accessToken))
            {
                context.Request.Headers["Authorization"] = "Bearer " + accessToken;
            }

            await next.Invoke().ConfigureAwait(false);
        });

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 LukaszK
Solution 2 A. Almazidi