2

I have a SignalR Core hub which has a dependency on a service. That service itself has it's own dependencies and one of them requires access to the current ClaimsPrincipal.

I know, that I can access the ClaimsPrincipal inside the hub using the Context.User property and pass it as a parameter to the service, which can also pass it as a parameter and so on. But I really don't like to pollute the service API by passing this kind of ambient info as a parameter.

I've tried to use the IHttpContextAccessor as described in: https://learn.microsoft.com/en-us/aspnet/core/migration/claimsprincipal-current?view=aspnetcore-2.2 This seems to be working with a simple SignalR setup, but it isn't working with the Azure SignalR service, which will be our production setup.

Is there a reliable way how to get the ClaimsPrincipal outside of the hub that will work for both a simple local setup and Azure SignalR service?

1 Answers1

2

In the current SignalR version (1.1.0) there is no support for this. I've created a feature request: https://github.com/dotnet/aspnetcore/issues/18657 but it was rejected. Eventually, I've ended up doing it like this:

services.AddSingleton(typeof(HubDispatcher<>), typeof(HttpContextSettingHubDispatcher<>));
public class HttpContextSettingHubDispatcher<THub> : DefaultHubDispatcher<THub> where THub : Hub
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public HttpContextSettingHubDispatcher(IServiceScopeFactory serviceScopeFactory, IHubContext<THub> hubContext,
        IOptions<HubOptions<THub>> hubOptions, IOptions<HubOptions> globalHubOptions,
        ILogger<DefaultHubDispatcher<THub>> logger, IHttpContextAccessor httpContextAccessor) :
        base(serviceScopeFactory, hubContext, hubOptions, globalHubOptions, logger)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public override async Task OnConnectedAsync(HubConnectionContext connection)
    {
        await InvokeWithContext(connection, () => base.OnConnectedAsync(connection));
    }

    public override async Task OnDisconnectedAsync(HubConnectionContext connection, Exception exception)
    {
        await InvokeWithContext(connection, () => base.OnDisconnectedAsync(connection, exception));
    }

    public override async Task DispatchMessageAsync(HubConnectionContext connection, HubMessage hubMessage)
    {
        switch (hubMessage)
        {
            case InvocationMessage _:
            case StreamInvocationMessage _:
                await InvokeWithContext(connection, () => base.DispatchMessageAsync(connection, hubMessage));
                break;
            default:
                await base.DispatchMessageAsync(connection, hubMessage);
                break;
        }
    }

    private async Task InvokeWithContext(HubConnectionContext connection, Func<Task> action)
    {
        var cleanup = false;
        if (_httpContextAccessor.HttpContext == null)
        {
            _httpContextAccessor.HttpContext = connection.GetHttpContext();
            cleanup = true;
        }
        await action();
        if (cleanup)
        {
            _httpContextAccessor.HttpContext = null;
        }
    }
}
  • I'm running into the same problem. So far I only found this workaround, which I have trouble when using. What packages did you install? I get `'HubConnectionContext' does not contain a definition for 'GetHttpContext'` – Paul Meems Jun 17 '20 at 14:17
  • It's in this class: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.gethttpcontextextensions?view=aspnetcore-2.1 nuget: Microsoft.AspNetCore.SignalR – Peter Mihalik Jun 19 '20 at 19:34
  • Apparently the defaulthubdispatcher is now internal so this workaround does no longer work does anybody have a solution for this ? – user1073694 Sep 04 '22 at 20:47