Yuresh Tharushika
Yuresh Tharushika

Reputation: 11

Can not authenticate SignalR chat hub

I'm writing a real time chat app with ASP.NET Core 8 Web API, SignalR and Angular. I use new authentication system introduced in .NET 8 with token authentication.

I have implemented authentication from the Angular frontend and it works correctly and I access authorized methods.

But when I try to authenticate my chat hub it does not recognize it as authenticated user.

Also Context.User?.Identity?.IsAuthenticated shows up as "false". But onConnectedAsync function is decorated as authorize and it works.

This is in my frontend chat service constructor where I build connection:

this.hubConnection = new signalR.HubConnectionBuilder()
      .withUrl(`${this.baseUrl}/chathub`, {
        accessTokenFactory: () => this.authService.getToken()
      })
      .build();

I checked the network tab. The access token is sent with the request.

This is my chat hub:

[Authorize]
public override async Task OnConnectedAsync()
{
    var userId = Context.UserIdentifier; // Get user ID from authentication
    await Clients.All.SendAsync("ReceiveSystemMessage", $"{userId} joined.");
    await base.OnConnectedAsync();
}

[Authorize]
public override async Task OnDisconnectedAsync(Exception exception)
{
    var userId = Context.UserIdentifier; // Get user ID from authentication
    await Clients.All.SendAsync("ReceiveSystemMessage", $"{userId} left.");
    await base.OnDisconnectedAsync(exception);
}

It connects successfully and sends messages also. But the system message only shows " joined" without the id.

This is program.cs file:

// Add Authentication
builder.Services.AddAuthentication().AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddAuthorizationBuilder();
builder.Services.AddIdentityCore<User>()
    .AddEntityFrameworkStores<Context>()
    .AddApiEndpoints();

var app = builder.Build();

app.UseCors();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapIdentityApi<User>();

app.MapControllers();

app.MapHub<ChatHub>("/chathub");

app.Run();

Upvotes: 1

Views: 134

Answers (1)

Yuning Duan
Yuning Duan

Reputation: 1682

In standard Web API, the bearer token is sent in the HTTP header. However, when using WebSocket and server-sent events, the token is transmitted as a query string parameter, so the JWT bearer middleware needs to be used on the server to configure the bearer token authentication.

Here is an example where I configure the relevant authentication parameters and the details captured by the OnAuthenticationFailed event when the authentication fails, you can use it as a reference:

builder.Services.AddAuthentication(options =>
{
    
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidAudience = configuration["JWT:ValidAudience"],
        ValidIssuer = configuration["JWT:ValidIssuer"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Secret"]))
    };

    options.Authority = "https://localhost:7060"; 

    options.Events = new JwtBearerEvents
    {
        OnAuthenticationFailed = context =>
        {
            Console.WriteLine("Authentication failed: " + context.Exception.Message);
            return Task.CompletedTask;
        },
    
        OnMessageReceived = context =>
        {
            var accessToken = context.Request.Query["access_token"];

            
            var path = context.HttpContext.Request.Path;
            if (!string.IsNullOrEmpty(accessToken) &&
                (path.StartsWithSegments("/chatHub")))
            {
              
                context.Token = accessToken;
            }
            return Task.CompletedTask;
        }
    };
});

When I send a request from the client:

var connection = new signalR.HubConnectionBuilder()
    .withUrl('https://localhost:7060/chatHub', {
        accessTokenFactory: () => {
            var fixedToken = 'mytoken';
            return  fixedToken;
        }
    })
    .build();

My method responds:

enter image description here

And in your project environment, if there are multiple authentication schemes, you need to specify the corresponding scheme: [Authorize(AuthenticationSchemes ="Your_Bearer_Scheme")]

Upvotes: 0

Related Questions