Azure Functions development and configuration with Azure SignalR Service

Azure Functions applications can leverage the Azure SignalR Service bindings to add real-time capabilities. Client applications use client SDKs available in several languages to connect to Azure SignalR Service and receive real-time messages.

This article describes the concepts for developing and configuring an Azure Function app that is integrated with SignalR Service.

SignalR Service configuration

Azure SignalR Service can be configured in different modes. When used with Azure Functions, the service must be configured in Serverless mode.

In the Azure portal, locate the Settings page of your SignalR Service resource. Set the Service mode to Serverless.

SignalR Service Mode

Azure Functions development

A serverless real-time application built with Azure Functions and Azure SignalR Service typically requires two Azure Functions:

  • A "negotiate" function that the client calls to obtain a valid SignalR Service access token and service endpoint URL
  • One or more functions that handle messages from SignalR Service and send messages or manage group membership

negotiate function

A client application requires a valid access token to connect to Azure SignalR Service. An access token can be anonymous or authenticated to a given user ID. Serverless SignalR Service applications require an HTTP endpoint named "negotiate" to obtain a token and other connection information, such as the SignalR Service endpoint URL.

Use an HTTP triggered Azure Function and the SignalRConnectionInfo input binding to generate the connection information object. The function must have an HTTP route that ends in /negotiate.

With class based model in C#, you don't need SignalRConnectionInfo input binding and can add custom claims much easier. See Negotiate experience in class based model

For more information on how to create the negotiate function, see the SignalRConnectionInfo input binding reference.

To learn about how to create an authenticated token, refer to Using App Service Authentication.

Handle messages sent from SignalR Service

Use the SignalR Trigger binding to handle messages sent from SignalR Service. You can be triggered when clients send messages or clients get connected or disconnected.

For more information, see the SignalR trigger binding reference.

You also need to configure your function endpoint as an upstream so that service will trigger the function where there is message from client. For more information about how to configure upstream, please refer to this doc.

Sending messages and managing group membership

Use the SignalR output binding to send messages to clients connected to Azure SignalR Service. You can broadcast messages to all clients, or you can send them to a subset of clients that are authenticated with a specific user ID or have been added to a specific group.

Users can be added to one or more groups. You can also use the SignalR output binding to add or remove users to/from groups.

For more information, see the SignalR output binding reference.

SignalR Hubs

SignalR has a concept of "hubs". Each client connection and each message sent from Azure Functions is scoped to a specific hub. You can use hubs as a way to separate your connections and messages into logical namespaces.

Class based model

The class based model is dedicated for C#. With class based model can have a consistent SignalR server-side programming experience. It has the following features.

  • Less configuration work: The class name is used as HubName, the method name is used as Event and the Category is decided automatically according to method name.
  • Auto parameter binding: Neither ParameterNames nor attribute [SignalRParameter] is needed. Parameters are auto bound to arguments of Azure Function method in order.
  • Convenient output and negotiate experience.

The following codes demonstrate these features:

public class SignalRTestHub : ServerlessHub
{
    [FunctionName("negotiate")]
    public SignalRConnectionInfo Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req)
    {
        return Negotiate(req.Headers["x-ms-signalr-user-id"], GetClaims(req.Headers["Authorization"]));
    }

    [FunctionName(nameof(OnConnected))]
    public async Task OnConnected([SignalRTrigger]InvocationContext invocationContext, ILogger logger)
    {
        await Clients.All.SendAsync(NewConnectionTarget, new NewConnection(invocationContext.ConnectionId));
        logger.LogInformation($"{invocationContext.ConnectionId} has connected");
    }

    [FunctionName(nameof(Broadcast))]
    public async Task Broadcast([SignalRTrigger]InvocationContext invocationContext, string message, ILogger logger)
    {
        await Clients.All.SendAsync(NewMessageTarget, new NewMessage(invocationContext, message));
        logger.LogInformation($"{invocationContext.ConnectionId} broadcast {message}");
    }

    [FunctionName(nameof(OnDisconnected))]
    public void OnDisconnected([SignalRTrigger]InvocationContext invocationContext)
    {
    }
}

All functions that want to leverage class based model need to be the method of class that inherits from ServerlessHub. The class name SignalRTestHub in the sample is the hub name.

Define hub method

All the hub methods must have an argument of InvocationContext decorated by [SignalRTrigger] attribute and use parameterless constructor. Then the method name is treated as parameter event.

By default, category=messages except the method name is one of the following names:

  • OnConnected: Treated as category=connections, event=connected
  • OnDisconnected: Treated as category=connections, event=disconnected

Parameter binding experience

In class based model, [SignalRParameter] is unnecessary because all the arguments are marked as [SignalRParameter] by default except it is one of the following situations:

  • The argument is decorated by a binding attribute.
  • The argument's type is ILogger or CancellationToken
  • The argument is decorated by attribute [SignalRIgnore]

Negotiate experience in class based model

Instead of using SignalR input binding [SignalR], negotiation in class based model can be more flexible. Base class ServerlessHub has a method

SignalRConnectionInfo Negotiate(string userId = null, IList<Claim> claims = null, TimeSpan? lifeTime = null)

This features user customizes userId or claims during the function execution.

Use SignalRFilterAttribute

User can inherit and implement the abstract class SignalRFilterAttribute. If exceptions are thrown in FilterAsync, 403 Forbidden will be sent back to clients.

The following sample demonstrates how to implement a customer filter that only allows the admin to invoke broadcast.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
internal class FunctionAuthorizeAttribute: SignalRFilterAttribute
{
    private const string AdminKey = "admin";

    public override Task FilterAsync(InvocationContext invocationContext, CancellationToken cancellationToken)
    {
        if (invocationContext.Claims.TryGetValue(AdminKey, out var value) &&
            bool.TryParse(value, out var isAdmin) &&
            isAdmin)
        {
            return Task.CompletedTask;
        }

        throw new Exception($"{invocationContext.ConnectionId} doesn't have admin role");
    }
}

Leverage the attribute to authorize the function.

[FunctionAuthorize]
[FunctionName(nameof(Broadcast))]
public async Task Broadcast([SignalRTrigger]InvocationContext invocationContext, string message, ILogger logger)
{
}

Client development

SignalR client applications can leverage the SignalR client SDK in one of several languages to easily connect to and receive messages from Azure SignalR Service.

Configuring a client connection

To connect to SignalR Service, a client must complete a successful connection negotiation that consists of these steps:

  1. Make a request to the negotiate HTTP endpoint discussed above to obtain valid connection information
  2. Connect to SignalR Service using the service endpoint URL and access token obtained from the negotiate endpoint

SignalR client SDKs already contain the logic required to perform the negotiation handshake. Pass the negotiate endpoint's URL, minus the negotiate segment, to the SDK's HubConnectionBuilder. Here is an example in JavaScript:

const connection = new signalR.HubConnectionBuilder()
  .withUrl('https://my-signalr-function-app.azurewebsites.net/api')
  .build()

By convention, the SDK automatically appends /negotiate to the URL and uses it to begin the negotiation.

Note

If you are using the JavaScript/TypeScript SDK in a browser, you need to enable cross-origin resource sharing (CORS) on your Function App.

For more information on how to use the SignalR client SDK, refer to the documentation for your language:

Sending messages from a client to the service

If you have upstream configured for your SignalR resource, you can send messages from client to your Azure Functions using any SignalR client. Here is an example in JavaScript:

connection.send('method1', 'arg1', 'arg2');

Azure Functions configuration

Azure Function apps that integrate with Azure SignalR Service can be deployed like any typical Azure Function app, using techniques such as continuously deployment, zip deployment, and run from package.

However, there are a couple of special considerations for apps that use the SignalR Service bindings. If the client runs in a browser, CORS must be enabled. And if the app requires authentication, you can integrate the negotiate endpoint with App Service Authentication.

Enabling CORS

The JavaScript/TypeScript client makes HTTP requests to the negotiate function to initiate the connection negotiation. When the client application is hosted on a different domain than the Azure Function app, cross-origin resource sharing (CORS) must be enabled on the Function app or the browser will block the requests.

Localhost

When running the Function app on your local computer, you can add a Host section to local.settings.json to enable CORS. In the Host section, add two properties:

  • CORS - enter the base URL that is the origin the client application
  • CORSCredentials - set it to true to allow "withCredentials" requests

Example:

{
  "IsEncrypted": false,
  "Values": {
    // values
  },
  "Host": {
    "CORS": "http://localhost:8080",
    "CORSCredentials": true
  }
}

Cloud - Azure Functions CORS

To enable CORS on an Azure Function app, go to the CORS configuration screen under the Platform features tab of your Function app in the Azure portal.

Note

CORS configuration is not yet available in Azure Functions Linux Consumption plan. Use Azure API Management to enable CORS.

CORS with Access-Control-Allow-Credentials must be enabled for the SignalR client to call the negotiate function. Select the checkbox to enable it.

In the Allowed origins section, add an entry with the origin base URL of your web application.

Configuring CORS

Cloud - Azure API Management

Azure API Management provides an API gateway that adds capabilities to existing back-end services. You can use it to add CORS to your function app. It offers a consumption tier with pay-per-action pricing and a monthly free grant.

Refer to the API Management documentation for information on how to import an Azure Function app. Once imported, you can add an inbound policy to enable CORS with Access-Control-Allow-Credentials support.

<cors allow-credentials="true">
  <allowed-origins>
    <origin>https://azure-samples.github.io</origin>
  </allowed-origins>
  <allowed-methods>
    <method>GET</method>
    <method>POST</method>
  </allowed-methods>
  <allowed-headers>
    <header>*</header>
  </allowed-headers>
  <expose-headers>
    <header>*</header>
  </expose-headers>
</cors>

Configure your SignalR clients to use the API Management URL.

Using App Service Authentication

Azure Functions has built-in authentication, supporting popular providers such as Facebook, Twitter, Microsoft Account, Google, and Azure Active Directory. This feature can be integrated with the SignalRConnectionInfo binding to create connections to Azure SignalR Service that have been authenticated to a user ID. Your application can send messages using the SignalR output binding that are targeted to that user ID.

In the Azure portal, in your Function app's Platform features tab, open the Authentication/authorization settings window. Follow the documentation for App Service Authentication to configure authentication using an identity provider of your choice.

Once configured, authenticated HTTP requests will include x-ms-client-principal-name and x-ms-client-principal-id headers containing the authenticated identity's username and user ID, respectively.

You can use these headers in your SignalRConnectionInfo binding configuration to create authenticated connections. Here is an example C# negotiate function that uses the x-ms-client-principal-id header.

[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;
}

You can then send messages to that user by setting the UserId property of a SignalR message.

[FunctionName("SendMessage")]
public static Task SendMessage(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
    [SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
    return signalRMessages.AddAsync(
        new SignalRMessage
        {
            // the message will only be sent to these user IDs
            UserId = "userId1",
            Target = "newMessage",
            Arguments = new [] { message }
        });
}

For information on other languages, see the Azure SignalR Service bindings for Azure Functions reference.

Next steps

In this article, you have learned how to develop and configure serverless SignalR Service applications using Azure Functions. Try creating an application yourself using one of the quick starts or tutorials on the SignalR Service overview page.