How to get the userid of a user in Signalr hub

Amjad Khan 21 Reputation points
2022-05-22T13:41:05.93+00:00

I have a SignalR hub in a Blazor wasm application on .NET6, using .NET core Identity and IdnetityServer4. In the hub, in order to send messages to particular user, I want to maintain a group for each user. So in my hub, I have this code:

[Authorize]
        public override async Task OnConnectedAsync()
        {
            string user = Context.User.Identity.Name;
            //enter code here to keep track of connected clients
            var userName = Context.User.FindFirst(ClaimTypes.NameIdentifier); // get the username of the connected user

            await Groups.AddToGroupAsync(Context.ConnectionId, $"user_{userName}");

            await base.OnConnectedAsync();
        }

However, both user and userName are null even though a connection is granted by SignalR. How can I get the userName in this hub method?

I even tried the following code, but jwtToken is null (even though user is logged in):

public override Task OnConnectedAsync()
{
    var httpContext = Context.GetHttpContext();
    if (httpContext != null)
    {
        var jwtToken = httpContext.Request.Query["access_token"];
        var handler = new JwtSecurityTokenHandler();
        if (!string.IsNullOrEmpty(jwtToken))
        {
            var token = handler.ReadJwtToken(jwtToken);
            var tokenS = token as JwtSecurityToken;

           // replace email with your claim name
            var jti = tokenS.Claims.First(claim => claim.Type == "email").Value;
            if (jti != null && jti != "")
            {
                Groups.AddToGroupAsync(Context.ConnectionId, jti);
            }
        }
    }
    return base.OnConnectedAsync();
}

If I use the [Authorize] attribute on the hub class, I get an UnAuthorized 401 error when connecting to the hub from the client:

hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/broadcaster"))
            .Build();
...
await hubConnection.StartAsync(); //returns 401 error

Here is the startup.cs:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PakPx1.Server.Data;
using PakPx1.Server.Models;
using System;
using Syncfusion.Blazor;
using System.Linq;
using System.Threading.Tasks;
using PakPx1.Server.Hubs;
using PakPx1.Shared.Models;
using PakPx1.Client;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using PakPx1.Server.Utils;
using Microsoft.AspNetCore.SignalR;

namespace PakPx1.Server
{
    public class Startup
    {

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            //services.AddDbContext<ApplicationDbContext>(options =>
            //    options.UseSqlServer(
            //        Configuration.GetConnectionString("DefaultConnection")));
            services.AddDatabaseDeveloperPageExceptionFilter();
            services.AddSyncfusionBlazor();
            services.AddDbContext<ApplicationDbContext>(options =>
            options.UseMySql(Configuration.GetConnectionString("DefaultConnection"), new MySqlServerVersion(new Version(5, 6, 48))));
            services.AddOptions();
            services.AddControllers();

            services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>();


            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
                {
                    options.IdentityResources["openid"].UserClaims.Add("role");
                    options.ApiResources.Single().UserClaims.Add("role");

                });


            services.AddAuthentication()
                .AddIdentityServerJwt();

            services.AddHttpContextAccessor();
            services.AddAuthorization();

            //services.AddSingleton<IUserIdProvider, NameUserIdProvider>();

            services.AddControllersWithViews();
            services.AddRazorPages();
            services.AddResponseCompression(opts =>
            {
                opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                    new[] { "application/octet-stream" });
            });
            services.Configure<IdentityOptions>(options =>
            {
                // Password settings
                options.Password.RequireDigit = false;
                options.Password.RequiredLength = 4;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = false;
                options.Password.RequireLowercase = false;

            });
            services.AddServerSideBlazor();
            services.AddResponseCompression(opts =>
            {
                opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                    new[] { "application/octet-stream" });
            });
            services.AddMvc().AddMvcOptions(options =>
            {
                options.EnableEndpointRouting = false;
            });
            services.AddMvcCore(options => options.OutputFormatters.Add(new XmlSerializerOutputFormatter()));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
            services.AddMvc().AddXmlDataContractSerializerFormatters();
            services.AddSignalR();


            //services.AddCors(options =>
            //{
            //    options.AddPolicy("EnableCORS", builder =>
            //    {
            //        builder.AllowAnyOrigin().AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials().Build();
            //    });
            //});
            //services.AddMvc(option => option.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);


        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
        {
            app.UseResponseCompression();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseMigrationsEndPoint();
                app.UseWebAssemblyDebugging();
            }
            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.UseIdentityServer();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                endpoints.MapHub<Broadcaster>("/broadcaster");
                endpoints.MapFallbackToFile("index.html");
            });
            CreateRoles(serviceProvider).Wait();

        }

        private async Task CreateRoles(IServiceProvider serviceProvider)
        {
            var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
            serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();

            string[] roleNames = { "Admin", "Business Manager", "Client", "Settlement Manager", "VOTClient" };
            IdentityResult roleResult;

            foreach (var roleName in roleNames)
            {
                var roleExist = await RoleManager.RoleExistsAsync(roleName);
                if (!roleExist)
                {
                    roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
                }
            }

        }




    }
}
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,401 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Amjad Khan 21 Reputation points
    2022-05-23T12:50:13.357+00:00

    I found the solution. Turns out that hub creation requires the following code:
    From this:

    hubConnection = new HubConnectionBuilder()
                .WithUrl(NavigationManager.ToAbsoluteUri("/broadcaster"))
                .Build();
    

    To this:

    hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/broadcaster"), options =>
            {
                options.AccessTokenProvider = async () =>
                {
                    var accessTokenResult = await AccessTokenProvider.RequestAccessToken();
                    accessTokenResult.TryGetToken(out var accessToken);
                    return accessToken.Value;
                };
            })
            .Build();
    

    This SO post was helpful:
    https://stackoverflow.com/questions/63083267/signalr-hub-authorization-for-blazor-webassembly-with-identity

    0 comments No comments