Search code examples
signalrazure-functions

Serverless SignalR authentication with JWT in Azure Function


I am using a SignalRConnectionInfo input binding in an Azure function. In this binding I need to provide the userId of the current principal, however by default the Azure function only supports the use of pre-defined headers from App Service Authentication, as outlined here, and in the below example:

[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
  [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req, 
  [SignalRConnectionInfo(HubName = "foo", UserId = "{headers.x-ms-client-principal-id}")] SignalRConnectionInfo connectionInfo)
{
  return connectionInfo;
}

As I am not using App Service authentication, and do not wish to, this is not suitable for my needs.

What I do currently have is a JWT which the user provides to an API we host in another App Service in order to authenticate the request and identify themselves. How can I amend the UserId property of the SignalRConnectionInfo binding to retrieve the UserId from the claims within that JWT?


Solution

  • AFAIK there isn't a way to extract information from a JWT using binding expressions.

    Instead, you will have to use Runtime Binding to first extract the information from the JWT and then use it in the binding to get the SignalR Connection Information.

    Here's a functional example where the JWT is retrieved from the Authorization header, validated, then applied to the SignalRConnectionInfo attribute.

    [FunctionName("Negotiate")]
    public static async Task<IActionResult> Run(
      [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "negotiate")] HttpRequest req,
      IBinder binder)
    {
      if (!req.Headers.ContainsKey("Authorization"))
        return new UnauthorizedResult();
    
      var principal = TryGetPrincipal(req.Headers["Authorization"].ToString());
      if (principal == null) 
        return new UnauthorizedResult();
    
      var connectionInfo = await binder.BindAsync<SignalRConnectionInfo>(new SignalRConnectionInfoAttribute
      {
        HubName = _hubName,
        UserId = principal.FindFirst(ClaimTypes.NameIdentifier).Value
      });
      return new OkObjectResult(connectionInfo);
    }
    
    public ClaimsPrincipal TryGetPrincipal(string token) 
    {
      // implementation varies based on authorization type...
    }