none
SignalRConnectionInfo in Function App - Trouble with UserId RRS feed

  • Question

  • I have a function app that uses the SignalR service. The negotiate function in the function app is called from an Angular 7 front end which passes a bearer token received from AAD v2. In the negotiate function, I have an input binding for the SignalRConnectionInfo where I set the HubName. This returns the ConnectionInfo object with the AccessToken and URL so the client app can connect to the SignalR service. I also have an Event Grid Trigger so I can respond to the OnClientConnectionConnected and OnClientConnectionDisconnected events from the Event Grid. In that Event Grid Trigger I need to get ahold of the group memberships for that particular user so I can assign the connection to a SignalR group.

    [FunctionName("negotiate")]
            public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
            [SignalRConnectionInfo(HubName = "HealthMessages")]SignalRConnectionInfo connectionInfo
                , ILogger log)
            {            
                log.LogInformation($"C# Event Hub trigger function processed a negotiate request" + req.ToString());            
                IServiceManager smb = new ServiceManagerBuilder()
                    .WithOptions(option =>
                    {
                        option.ConnectionString = Environment.GetEnvironmentVariable("AzureSignalRConnectionString");
                        option.ServiceTransportType = ServiceTransportType.Transient;                    
                    })
                    .Build();
    
                Task<IServiceHubContext> hubContext = smb.CreateHubContextAsync(HubName);
                hubContext.Wait();            
    
                // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
                return new OkObjectResult(connectionInfo);
            }

    In the SignalRConnectionInfo input binding, I really need to be able to set/bind the UserId property of the connectionInfo variable so that it shows up in the Event Grid Trigger handler method but I can't seem to. The following code snippet is what I'd like to do but it is not supported in AAD v2 per MS:

    [FunctionName("negotiate")]
    public static SignalRConnectionInfo Negotiate(
        [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req, 
        [SignalRConnectionInfo
            (HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]
            SignalRConnectionInfo connectionInfo)
    {
        // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
        return connectionInfo;
    }

    I can get ahold of the bearer token through the HttpRequest but even if I do, the AccessToken of the SignalRConnectionInfo is already set and there is no way to set the UserId even if I could.

    I should add that the x-ms-client-principal-* headers are not present in the request being sent by the front end but maybe that's why AAD v2 isn't supported. I don't know.

    Does anyone have any thoughts on how I might accomplish this?

    ***************** Update ***************

    I have succeeded in adding the bearer token to the UserId property so now I can validate and decode it to get the oid of the user and use MS Graph to get the group membership. While klugy, it should work. I'd still be interested in any ideas.



    Friday, August 16, 2019 3:35 PM

Answers

  • Ryan,

    Per Anthony Chu, I added the IBinder binder input binding and was able to create my SignalRConnectionInfo object using the hub name and the user id like so:

    [FunctionName("negotiate")]
            public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
            IBinder binder,
            ILogger log)
            {            
                log.LogInformation($"C# Event Hub trigger function processed a negotiate request" + req.ToString());
                
                if (req.Headers.ContainsKey(AUTH_HEADER_NAME) &&
                    req.Headers[AUTH_HEADER_NAME].ToString().StartsWith(BEARER_PREFIX))
                {
                    string token = req.Headers[AUTH_HEADER_NAME].ToString().Substring(BEARER_PREFIX.Length);
                    string userId = ExtractUserId(token);
                    SignalRConnectionInfo connectionInfo = binder.Bind<SignalRConnectionInfo>(new SignalRConnectionInfoAttribute { HubName = HubNameInternal, UserId = userId }); ;
    
                    // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
                    return new OkObjectResult(connectionInfo);
                }
                else
                {
                    return new BadRequestObjectResult("No access token submitted.");
                }
            }

    Where ExtractUserId(token) decoded the token and returned the user id. Also, I was informed that I could ditch the Event Grid Trigger and just assign the users to their groups in the negotiate function before the front end app called back to make the connection. That was good news. Thanks for your response.

    Tuesday, August 20, 2019 6:59 PM

All replies

  • Hi Evan,

    I've been told that we'll be updating our docs; nonetheless, v2 is supported. You'll have to change your issuer endpoint to the v2 URI though. If you already done this, then please let me know as it could indicate a bug.

    I look forward to hearing from you.


    Thanks in advance, Ryan

    Tuesday, August 20, 2019 5:56 PM
    Moderator
  • Ryan,

    Per Anthony Chu, I added the IBinder binder input binding and was able to create my SignalRConnectionInfo object using the hub name and the user id like so:

    [FunctionName("negotiate")]
            public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
            IBinder binder,
            ILogger log)
            {            
                log.LogInformation($"C# Event Hub trigger function processed a negotiate request" + req.ToString());
                
                if (req.Headers.ContainsKey(AUTH_HEADER_NAME) &&
                    req.Headers[AUTH_HEADER_NAME].ToString().StartsWith(BEARER_PREFIX))
                {
                    string token = req.Headers[AUTH_HEADER_NAME].ToString().Substring(BEARER_PREFIX.Length);
                    string userId = ExtractUserId(token);
                    SignalRConnectionInfo connectionInfo = binder.Bind<SignalRConnectionInfo>(new SignalRConnectionInfoAttribute { HubName = HubNameInternal, UserId = userId }); ;
    
                    // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
                    return new OkObjectResult(connectionInfo);
                }
                else
                {
                    return new BadRequestObjectResult("No access token submitted.");
                }
            }

    Where ExtractUserId(token) decoded the token and returned the user id. Also, I was informed that I could ditch the Event Grid Trigger and just assign the users to their groups in the negotiate function before the front end app called back to make the connection. That was good news. Thanks for your response.

    Tuesday, August 20, 2019 6:59 PM
  • Glad you were able to get it sorted out Evan. Feel free to reach back out to us if you need any further assistance.

    Thanks in advance, Ryan

    Thursday, August 22, 2019 6:45 PM
    Moderator