Edit

Share via


Azure Web PubSub input bindings for Azure Functions

Our extension provides two input binding targeting different needs.

  • WebPubSubConnection

    To let a client connect to Azure Web PubSub Service, it must know the service endpoint URL and a valid access token. The WebPubSubConnection input binding produces required information, so client doesn't need to handle this token generation itself. The token is time-limited and can authenticate a specific user to a connection. Therefore, don't cache the token or share it between clients. An HTTP trigger working with this input binding can be used for clients to retrieve the connection information.

  • WebPubSubContext

    When using is Static Web Apps, HttpTrigger is the only supported trigger and under Web PubSub scenario, we provide the WebPubSubContext input binding helps users deserialize upstream http request from service side under Web PubSub protocols. So customers can get similar results comparing to WebPubSubTrigger to easily handle in functions. When used with HttpTrigger, customer requires to configure the HttpTrigger exposed url in event handler accordingly.

WebPubSubConnection

Example

The following example shows an HTTP trigger function that acquires Web PubSub connection information using the input binding and returns it over HTTP. In following example, the UserId is passed in through client request query part like ?userid={User-A}.

[Function("WebPubSubConnectionInputBinding")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req,
[WebPubSubConnectionInput(Hub = "<hub>", , UserId = "{query.userid}", Connection = "<web_pubsub_connection_name>")] WebPubSubConnection connectionInfo)
{
    var response = req.CreateResponse(HttpStatusCode.OK);
    response.WriteAsJsonAsync(connectionInfo);
    return response;
}
const { app, input } = require('@azure/functions');

const connection = input.generic({
    type: 'webPubSubConnection',
    name: 'connection',
    userId: '{query.userId}',
    hub: '<hub>'
});

app.http('negotiate', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    extraInputs: [connection],
    handler: async (request, context) => {
        return { body: JSON.stringify(context.extraInputs.get('connection')) };
    },
});

Create a folder negotiate and update negotiate/function.json and copy following JSON codes.

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    },
    {
      "type": "webPubSubConnection",
      "name": "connection",
      "userId": "{query.userid}",
      "hub": "<hub>",
      "direction": "in"
    }
  ]
}

Define function in negotiate/init.py.

import logging

import azure.functions as func

def main(req: func.HttpRequest, connection) -> func.HttpResponse:
    return func.HttpResponse(connection)

Note

Complete samples for this language are pending

Note

The Web PubSub extensions for Java isn't supported yet.

Get authenticated user ID

If the function is triggered by an authenticated client, you can add a user ID claim to the generated token. You can easily add authentication to a function app using App Service Authentication.

App Service Authentication sets HTTP headers named x-ms-client-principal-id and x-ms-client-principal-name that contain the authenticated user's client principal ID and name, respectively.

You can set the UserId property of the binding to the value from either header using a binding expression: {headers.x-ms-client-principal-id} or {headers.x-ms-client-principal-name}.

[Function("WebPubSubConnectionInputBinding")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req,
[WebPubSubConnectionInput(Hub = "<hub>", , UserId = "{headers.x-ms-client-principal-id}", Connection = "<web_pubsub_connection_name>")] WebPubSubConnection connectionInfo)
{
    var response = req.CreateResponse(HttpStatusCode.OK);
    response.WriteAsJsonAsync(connectionInfo);
    return response;
}
const { app, input } = require('@azure/functions');

const connection = input.generic({
    type: 'webPubSubConnection',
    name: 'connection',
    userId: '{headers.x-ms-client-principal-id}',
    hub: '<hub>'
});

app.http('negotiate', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    extraInputs: [connection],
    handler: async (request, context) => {
        return { body: JSON.stringify(context.extraInputs.get('connection')) };
    },
});

Create a folder negotiate and update negotiate/function.json and copy following JSON codes.

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    },
    {
      "type": "webPubSubConnection",
      "name": "connection",
      "userId": "{headers.x-ms-client-principal-id}",
      "hub": "<hub>",
      "direction": "in"
    }
  ]
}

Define function in negotiate/init.py.

import logging

import azure.functions as func

def main(req: func.HttpRequest, connection) -> func.HttpResponse:
    return func.HttpResponse(connection)

Note

Complete samples for this language are pending

Note

The Web PubSub extensions for Java isn't supported yet.

Configuration

The following table explains the binding configuration properties that you set in the function.json file and the WebPubSubConnection attribute.

function.json property Attribute property Description
type n/a Must be set to webPubSubConnection
direction n/a Must be set to in
name n/a Variable name used in function code for input connection binding object.
hub Hub Required - The value must be set to the name of the Web PubSub hub for the function to be triggered. We support set the value in attribute as higher priority, or it can be set in app settings as a global value.
userId UserId Optional - the value of the user identifier claim to be set in the access key token.
clientProtocol ClientProtocol Optional - The client protocol type. Valid values include default and mqtt.
For MQTT clients, you must set it to mqtt.
For other clients, you can omit the property or set it to default.
connection Connection Required - The name of the app setting that contains the Web PubSub Service connection string (defaults to "WebPubSubConnectionString").

Usage

WebPubSubConnection provides following properties.

Binding Name Binding Type Description
BaseUri Uri Web PubSub client connection uri.
Uri Uri Absolute Uri of the Web PubSub connection, contains AccessToken generated base on the request.
AccessToken string Generated AccessToken based on request UserId and service information.

WebPubSubConnection provides following properties.

Binding Name Description
baseUrl Web PubSub client connection uri.
url Absolute Uri of the Web PubSub connection, contains AccessToken generated base on the request.
accessToken Generated AccessToken based on request UserId and service information.

Note

The Web PubSub extensions for Java isn't supported yet.

More customization of generated token

Limited to the binding parameter types don't support a way to pass list nor array, the WebPubSubConnection isn't fully supported with all the parameters server SDK has, especially roles, and also includes groups and expiresAfter.

When customer needs to add roles or delay building the access token in the function, we suggest you to work with server SDK for C#.

[Function("WebPubSubConnectionCustomRoles")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req)
{
    var serviceClient = new WebPubSubServiceClient(new Uri(endpoint), "<hub>", "<web-pubsub-connection-string>");
    var userId = req.Query["userid"].FirstOrDefault();
    // your method to get custom roles.
    var roles = GetRoles(userId);
    var url = await serviceClient.GetClientAccessUriAsync(TimeSpan.FromMinutes(5), userId, roles);
    var response = req.CreateResponse(HttpStatusCode.OK);
    response.WriteString(url.ToString());
    return response;
}

When customer needs to add roles or delay building the access token in the function, we suggest you working with server SDK for JavaScript.

const { app } = require('@azure/functions');
const { WebPubSubServiceClient } = require('@azure/web-pubsub');
app.http('negotiate', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    handler: async (request, context) => {
        const serviceClient = new WebPubSubServiceClient(process.env.WebPubSubConnectionString, "<hub>");
        let token = await serviceClient.getAuthenticationToken({ userId: req.query.userid, roles: ["webpubsub.joinLeaveGroup", "webpubsub.sendToGroup"] });
        return { body: token.url };
    },
});

Note

Complete samples for this language are pending

Note

The Web PubSub extensions for Java isn't supported yet.

WebPubSubContext

Example

// validate method when upstream set as http://<func-host>/api/{event}
[Function("validate")]
public static HttpResponseData Validate(
    [HttpTrigger(AuthorizationLevel.Anonymous, "options")] HttpRequestData req,
    [WebPubSubContextInput] WebPubSubContext wpsReq)
{
    return BuildHttpResponseData(req, wpsReq.Response);
}

// Respond AbuseProtection to put header correctly.
private static HttpResponseData BuildHttpResponseData(HttpRequestData request, SimpleResponse wpsResponse)
{
    var response = request.CreateResponse();
    response.StatusCode = (HttpStatusCode)wpsResponse.Status;
    response.Body = response.Body;
    foreach (var header in wpsResponse.Headers)
    {
        response.Headers.Add(header.Key, header.Value);
    }
    return response;
}
const { app, input } = require('@azure/functions');

const wpsContext = input.generic({
    type: 'webPubSubContext',
    name: 'wpsContext'
});

app.http('connect', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    extraInputs: [wpsContext],
    handler: async (request, context) => {
        var wpsRequest = context.extraInputs.get('wpsContext');

        return { "userId": wpsRequest.request.connectionContext.userId };
    }
});

Note

Complete samples for this language are pending

Note

The Web PubSub extensions for Java isn't supported yet.

Configuration

The following table explains the binding configuration properties that you set in the functions.json file and the WebPubSubContext attribute.

function.json property Attribute property Description
type n/a Must be set to webPubSubContext.
direction n/a Must be set to in.
name n/a Variable name used in function code for input Web PubSub request.
connection Connection Optional - the name of an app settings or setting collection that specifies the upstream Azure Web PubSub service. The value is used for Abuse Protection and Signature validation. The value is auto resolved with "WebPubSubConnectionString" by default. And null means the validation isn't needed and always succeed.

Important

For optimal security, your function app should use managed identities when connecting to the Web PubSub service instead of using a connection string, which contains a shared secret key. For more information, see Authorize a managed identity request by using Microsoft Entra ID.

Usage

WebPubSubContext provides following properties.

Binding Name Binding Type Description Properties
request WebPubSubEventRequest Request from client, see following table for details. WebPubSubConnectionContext from request header and other properties deserialized from request body describe the request, for example, Reason for DisconnectedEventRequest.
response HttpResponseMessage Extension builds response mainly for AbuseProtection and errors cases. -
errorMessage string Describe the error details when processing the upstream request. -
hasError bool Flag to indicate whether it's a valid Web PubSub upstream request. -
isPreflight bool Flag to indicate whether it's a preflight request of AbuseProtection. -

For WebPubSubEventRequest, it's deserialized to different classes that provide different information about the request scenario. For PreflightRequest or not valid cases, user can check the flags IsPreflight and HasError to know. We suggest you to return system build response WebPubSubContext.Response directly, or customer can log errors on demand. In different scenarios, customer can read the request properties as following.

Derived Class Description Properties
PreflightRequest Used in AbuseProtection when IsPreflight is true -
ConnectEventRequest Used in system Connect event type Claims, Query, Subprotocols, ClientCertificates
ConnectedEventRequest Used in system Connected event type -
UserEventRequest Used in user event type Data, DataType
DisconnectedEventRequest Used in system Disconnected event type Reason

Note

Though the WebPubSubContext is an input binding provides similar request deserialize way under HttpTrigger comparing to WebPubSubTrigger, there's limitations, i.e. connection state post merge isn't supported. The return response is still respected by the service side, but users require to build the response themselves. If users have needs to set the event response, you should return a HttpResponseMessage contains ConnectEventResponse or messages for user event as response body and put connection state with key ce-connectionstate in response header.