Search code examples
c#.netazureazure-functionsazure-signalr

How to get the User ID in a EventGrid trigger Function App event?


I have an EventGrid Trigger Azure Function (using the isolated worker on .net7) that is called each time a client connects a SignalR hub. It is setup like this:

[Function("v1-on-connectivity-events-changed")]
public async Task OnConnectivityEventsChangedAsync
(
    [EventGridTrigger]
    EventGridConnectivityEvent eventGridConnectivityEvent,
    FunctionContext functionContext
)
{
  _logger.LogWarning("Event grid trigger received. Data='{ConnectionInfo}'", JsonSerializer.Serialize(eventGridConnectivityEvent));
  ...
}

On the client side, the application connects the SignalR via the Negotiate API like this:

_hubConnection = new HubConnectionBuilder()
                .WithUrl(hubEndpoint.AbsoluteUri, options =>
                {
                    //  Adds the application token to the options' header.
                    options.AccessTokenProvider = () => Task.FromResult(user.IdentityToken.AccessToken)!;
                    options.Headers.Add("userSid", user.IdentityToken.UserSid);   
                })
                .Build();

...

await _hubConnection.StartAsync();

Here is what the Negotiate function is doing which returns the URL along with an additional :

[Function("negotiate")]
public Task<SignalRConnectionInfo> OnNegotiateAsync
(
    [HttpTrigger(AuthorizationLevel.Anonymous, "POST")]
    HttpRequestData httpRequestData,
    [SignalRConnectionInfoInput
    (
       HubName = Constants.HubName,
       ConnectionStringSetting = "AzureSignalRConnectionString"
    )]
    SignalRConnectionInfo signalRConnectionInfo
)
{
        return ExecuteAsync(httpRequestData, async claimsPrincipal =>
        {
            IUserPrincipal userPrincipal = await RetrieveClaimsAsync(httpRequestData, claimsPrincipal);

            signalRConnectionInfo.Url += $"&userSid={userPrincipal.UserSid}";
            
            return signalRConnectionInfo;
        });
    }

The OnConnectivityEventsChangedAsync Function is properly called when the client gets connected. However, it does not provide all the information that I need: the User ID is missing.

Question

In the SignalR documentation, it is stated that the connected event should provide the user ID along with the connection ID. Here is what I get:

{
    "Id":"56f3ab25-11d7-41d6-acbb-8429a741d746",
"Topic":"/subscriptions/<subscription_id>/resourceGroups/<resource_group>/providers/Microsoft.SignalRService/SignalR/<signalr_instance>",
    "Subject":"hub/updates",
    "EventType":"Microsoft.SignalRService.ClientConnectionConnected",
    "EventTime":"2022-11-27T19:22:19.2349923Z",
    "Data": { 
         "timestamp":"2022-11-27T19:22:19.2349923Z",
         "hubName":"updates",
         "connectionId":"gmJM_GHVkXsQuS5C1LhdXXXXX"
    }
}

What should I do to get the User ID along with the Connection ID?


Solution

  • I have to set the UserId property of the SignalRConnectionInfoInputAttribute to indicate how to get the user ID. In my case, since the user ID is in the header of the HTTP request as userSid, I must do the following:

    [Function("negotiate")]
    public Task<SignalRConnectionInfo> OnNegotiateAsync
    (
        [HttpTrigger(AuthorizationLevel.Anonymous, "POST")]
        HttpRequestData httpRequestData,
        [SignalRConnectionInfoInput
        (
           HubName = Constants.HubName, 
           ConnectionStringSetting = "AzureSignalRConnectionString",
           UserId = "{headers.userSid}"
        )]
        SignalRConnectionInfo signalRConnectionInfo
    )
    {
       ...
       
       return signalRConnectionInfo;
    }