Search code examples
c#authenticationsignalrmicroservices

SignalR notification microservice - how to get user info


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?


Solution

  • 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.