0

I am trying to access Context.User.Identity.Name inside my signalr hub but I keep getting null. I added [Authorize] attribute on top of the hub class, but now I am getting 401 Unauthorized error.

Also, I am using asp.net Identity for my authorization method and it looks like my authorization cookie is getting pass through when trying to make a connection with the signalr server, but I am still unable to get Context.User.Identity.Name to show. What did I do wrong?

Here is my program.cs

global using BagiBagiDev.Shared;
global using BagiBagiDev.Server.Services.FollowerFollowingService;
global using BagiBagiDev.Server.Services.AuthService;
global using BagiBagiDev.Server.Services.UserService;
global using BagiBagiDev.Server.Services.TransactionService;
global using BagiBagiDev.Server.Services.DonatorService;
global using BagiBagiDev.Server.Services.SocialService;
global using BagiBagiDev.Server.Services.PaymentService;
global using BagiBagiDev.Server.Models;
global using BagiBagiDev.Server.Data;
global using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.Identity;
using BagiBagiDev.Server;
using Duende.IdentityServer.Services;
using BagiBagiDev.Server.Services;
using BagiBagiDev.Server.Hubs;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddClaimsPrincipalFactory<ApplicationClaimsPrincipalFactory>();

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddCookie(options =>
    {
        options.Cookie.Name = "BagiBagiAuth.Cookie";
        options.ExpireTimeSpan = TimeSpan.FromDays(1);
    })
    .AddIdentityServerJwt();
builder.Services.TryAddEnumerable(
    ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>,
        ConfigureJwtBearerOptions>());

builder.Services.AddResponseCompression(opts =>
{
    opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
          new[] { "application/octet-stream" });
});

builder.Services.AddTransient<IProfileService, ProfileService>();

builder.Services.AddScoped<IFollowerFollowingService, FollowerFollowingService>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ITransactionService, TransactionService>();
builder.Services.AddScoped<IDonatorService, DonatorService>();
builder.Services.AddScoped<ISocialService, SocialService>();
builder.Services.AddScoped<IPaymentService, PaymentService>();

builder.Services.AddSwaggerGen();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

builder.Services.AddSignalR();
builder.Services.AddSingleton<IUserIdProvider, NameUserIdProvider>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
    app.UseWebAssemblyDebugging();
    app.UseSwagger();
    app.UseSwaggerUI();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

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

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
    endpoints.MapControllers();
    endpoints.MapHub<PaymentHub>("/ws/payment");
});
app.MapFallbackToFile("index.html");

app.Run();

And here is my PaymentHub.cs

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

namespace BagiBagiDev.Server.Hubs
{
    [Authorize]
    public class PaymentHub : Hub
    {
        public async Task PaymentChange(string userId)
        {
            await Clients.All.SendAsync("Test", userId);
        }

        public override Task OnConnectedAsync()
        {
            return base.OnConnectedAsync();
        }
    }
}

Here is how I set up my connection on the client side

private HubConnection? hubConnection;

    protected override async Task OnInitializedAsync()
    {
        Balance = await userService.GetUserBalance();
        hubConnection = new HubConnectionBuilder()
            .WithUrl(navigationManager.ToAbsoluteUri($"/ws/payment"))
            .WithAutomaticReconnect()
            .Build();
        await hubConnection.StartAsync();
    }

I searched over the internet and tried to follow the documentation, but still no luck :(

  • Does this answer your question? [How does HttpContext.Current.User.Identity.Name know which usernames exist?](https://stackoverflow.com/questions/21117617/how-does-httpcontext-current-user-identity-name-know-which-usernames-exist) – wei Jun 12 '23 at 02:53
  • Hey @wei, thank you for your answer, but unfortunately it doesn't :(. My issue here is getting the Context.User.Identity.Name to show up in signalr hub. – Anthony Winoto Jun 12 '23 at 03:01
  • As you probably know Signalr can use many connection types to get the job done. It requires a bit of setup to get Auth to work. Both on the client and the server middleware. Can you show ConfigureJwtBearerOptions and the setup of your hubconnection in the client. – Brian Parker Jun 12 '23 at 03:19
  • Hey @BrianParker, Thank you so much for your answer. I've edited my questions to show how I connect my client with the hubconnection. As per your question regarding ConfigureJwtBearerOptions, I'm using asp.net Identity, which I believe is using cookie authentication method (correct me if I'm wrong), so I'm not too sure how to add pass that token to signalr – Anthony Winoto Jun 12 '23 at 03:37
  • You can refer to this [repo](https://github.com/xlegodroit/blazor-messenger/blob/develop/Server/Startup.cs#L58) for similar setup. It might be helpful to compare their code with yours. Also, take a look at their `IdBasedUserIdProvider` class [here](https://github.com/xlegodroit/blazor-messenger/blob/develop/Server/Services/Implementations/IdBasedUserIdProvider.cs) and compare it with your `NameUserIdProvider`. – wei Jun 12 '23 at 04:04
  • Hey @wei, I compared it with the repo you've linked but still no luck :( – Anthony Winoto Jun 12 '23 at 04:15

1 Answers1

1

Have you setup your client to send the token?

[Inject]
private AuthenticationStateProvider AuthenticationStateProvider { get; set; } = default!;

[Inject]
private IAccessTokenProvider AccessTokenProvider { get; set; } = default!;


protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder().WithUrl(
        url: new Uri(uriString: "https://localhost:7040/eventhub"), 
        SetConnectionOptions)
            .Build();

    hubConnection.On<EventMessage>(
        methodName: "ReceiveEventMessageAsync",
        ReceiveEventMessageAsync);

    await hubConnection.StartAsync();
}

private void SetConnectionOptions(HttpConnectionOptions options) =>
        options.AccessTokenProvider = async () => await GetAccessToken();


private async ValueTask<string> GetAccessToken()
{
    var accessTokenResult = await AccessTokenProvider.RequestAccessToken();
    accessTokenResult.TryGetToken(out var accessToken);
    return accessToken.Value;
}

Brian Parker
  • 11,946
  • 2
  • 31
  • 41
  • I just tried this and it works! I can't thank you enough for this.. I've been trying for hours and finally figured it out with your answer. Again, thank you so much! – Anthony Winoto Jun 12 '23 at 05:30
  • @AnthonyWinoto If you put [Authorize] on a hub method does it work? You may still have to configure some middleware on the server. – Brian Parker Jun 12 '23 at 05:41
  • Hey @Brian, yes I put [Authorize] attribute on the hub and it works. I was able to get all of the claims sent by the client as well :) – Anthony Winoto Jun 12 '23 at 05:52
  • @AnthonyWinoto I meant on a Hub method not the Hub itself. For example, some methods in the hub like banning or invitations to groups may be restricted to a role. – Brian Parker Jun 12 '23 at 06:29