I have built a little microservice in a docker container that is basically just a hub that an API can connect to to push notifications to a blazor client. Because it is a microservice, it runs in its own process and has no idea about authentication in the UI and API (the auth works with JWT token). Here is my OnConnectedAsync method :
private readonly static ConcurrentDictionary<string, string> _connectionMap = new();
public override Task OnConnectedAsync()
{
var connectionId = Context.ConnectionId;
var userName = Context.User?.Identity?.Name;
_connectionMap.TryAdd(connectionId, userName);
return base.OnConnectedAsync();
}
Unsurprizingly, everything in the Context.User is mostly null since it runs by itself and is not aware of all the auth going on in the other projects. I do not see an override I could use to somehow pass a user ID to my hub. I need to be able to associate a connectionID with a username so only that user can receive notifications, right now every client gets every notification.
How can I pass some kind of user identification to my SignalR microservice?
The solution is to supply the token in the request headers when connecting to the hub :
var token = await LocalStorageService.GetItemAsync<string>("authToken");
if (token is null)
return;
_hubConnection = new HubConnectionBuilder()
.WithUrl(Configuration["ServiceURLs:Notification"]!, options =>
{
options.HttpMessageHandlerFactory = (message) =>
{
if (message is HttpClientHandler clientHandler)
// bypass SSL certificate
clientHandler.ServerCertificateCustomValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => { return true; };
return message;
};
options.Headers.Add("Authorization", $"bearer {token}");
})
.WithAutomaticReconnect()
.Build();
Said token can then be retrieved in the OnConnectedAsync method in the hub :
var connectionId = Context.ConnectionId;
var tokenTemp = Context.Features.Get<IHttpContextFeature>()!.HttpContext!.Request.Headers.Authorization.ToString();
var token = tokenTemp[(tokenTemp.IndexOf(' ') + 1)..];
if (string.IsNullOrEmpty(token) || token == "bearer")
return Task.CompletedTask;
_connectionMap.TryAdd(connectionId, token);
Also note that I did not put the [Authorize] attribute on the hub class since, as far as it's aware, there is no auth whatsoever in its process. The connection from my API also doesn't supply a token when it connects, only when it wants to send a message so the hub method knows where to send it.