Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a way how to get the current ClaimsPrincipal in SignalR core outside of a Hub #18657

Closed
peto268 opened this issue Jan 29, 2020 · 3 comments
Labels
area-signalr Includes: SignalR clients and servers

Comments

@peto268
Copy link

peto268 commented Jan 29, 2020

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://docs.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.

It would be good to have either the IHttpContextAccessor available in all setups or some other similar interface that would allow to get the current principal.

@mkArtakMSFT mkArtakMSFT added the area-signalr Includes: SignalR clients and servers label Jan 29, 2020
@analogrelay
Copy link
Contributor

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.

This is recommended approach. Ambient context like IHttpContextAccessor can cause a lot of problems. We have IHttpContextAccessor because of the past history with HttpContext.Current but don't plan to add something similar here. If you want to do it, you can store the HubContext in a static property of type AsyncLocal<HubContext> and it will have the same value throughout the async call chain.

Closing as we don't plan to add ambient HubContext access.

@peto268
Copy link
Author

peto268 commented Jan 30, 2020

I don't really need the whole HubContext, not even the whole HttpContext, only the current user Principal.

I've ended up with 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;
		}
	}
}

This allows me to use the IHttpContextAccessor even with the Azure SignalR service.

@analogrelay
Copy link
Contributor

That seems like a fine workaround. Instead of IHttpContextAccessor though I would register your own singleton to hold the data you need in an AsyncLocal<T>. Otherwise you risk messing with other users of IHttpContextAccessor.

@ghost ghost locked as resolved and limited conversation to collaborators Mar 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-signalr Includes: SignalR clients and servers
Projects
None yet
Development

No branches or pull requests

3 participants