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));
}
}
}
}
}