Tutorial: Azure SignalR Service authentication with Azure Functions
A step by step tutorial to build a chat room with authentication and private messaging using Azure Functions, App Service Authentication, and SignalR Service.
Introduction
Technologies used
- Azure Functions - Backend API for authenticating users and sending chat messages
- Azure SignalR Service - Broadcast new messages to connected chat clients
- Azure Storage - Required by Azure Functions
Prerequisites
- An Azure account with an active subscription.
- If you don't have one, you can create one for free.
- Node.js (Version 18.x)
- Azure Functions Core Tools (Version 4)
Create essential resources on Azure
Create an Azure SignalR service resource
Your application will access a SignalR Service instance. Use the following steps to create a SignalR Service instance using the Azure portal.
Select on the Create a resource (+) button for creating a new Azure resource.
Search for SignalR Service and select it.
Select Create.
Enter the following information.
Name Value Resource group Create a new resource group with a unique name Resource name A unique name for the SignalR Service instance Region Select a region close to you Pricing Tier Free Service mode Serverless Select Review + Create.
Select Create.
Create an Azure Function App and an Azure Storage account
From the home page in the Azure portal, select on the Create a resource (+).
Search for Function App and select it.
Select Create.
Enter the following information.
Name Value Resource group Use the same resource group with your SignalR Service instance Function App name A unique name for the Function app instance Runtime stack Node.js Region Select a region close to you By default, a new Azure Storage account will also be created in the same resource group together with your function app. If you want to use another storage account in the function app, switch to Hosting tab to choose an account.
Select Review + Create, then select Create.
Create an Azure Functions project locally
Initialize a function app
From a command line, create a root folder for your project and change to the folder.
Execute the following command in your terminal to create a new JavaScript Functions project.
func init --worker-runtime node --language javascript --name my-app
By default, the generated project includes a host.json file containing the extension bundles which include the SignalR extension. For more information about extension bundles, see Register Azure Functions binding extensions.
Configure application settings
When running and debugging the Azure Functions runtime locally, application settings are read by the function app from local.settings.json. Update this file with the connection strings of the SignalR Service instance and the storage account that you created earlier.
Replace the content of local.settings.json with the following code:
{ "IsEncrypted": false, "Values": { "FUNCTIONS_WORKER_RUNTIME": "node", "AzureWebJobsStorage": "<your-storage-account-connection-string>", "AzureSignalRConnectionString": "<your-Azure-SignalR-connection-string>" } }
Enter the Azure SignalR Service connection string into the
AzureSignalRConnectionString
setting.Navigate to your SignalR Service in the Azure portal. In the Settings section, locate the Keys setting. Select the Copy button to the right of the connection string to copy it to your clipboard. You can use either the primary or secondary connection string.
Enter the storage account connection string into the
AzureWebJobsStorage
setting.Navigate to your storage account in the Azure portal. In the Security + networking section, locate the Access keys setting. Select the Copy button to the right of the connection string to copy it to your clipboard. You can use either the primary or secondary connection string.
Create a function to authenticate users to SignalR Service
When the chat app first opens in the browser, it requires valid connection credentials to connect to Azure SignalR Service. You'll create an HTTP triggered function named negotiate
in your function app to return this connection information.
Note
This function must be named negotiate
as the SignalR client requires an endpoint that ends in /negotiate
.
From the root project folder, create the
negotiate
function from a built-in template with the following command.func new --template "SignalR negotiate HTTP trigger" --name negotiate
Open negotiate/function.json to view the function binding configuration.
The function contains an HTTP trigger binding to receive requests from SignalR clients and a SignalR input binding to generate valid credentials for a client to connect to an Azure SignalR Service hub named
default
.{ "disabled": false, "bindings": [ { "authLevel": "anonymous", "type": "httpTrigger", "direction": "in", "methods": ["post"], "name": "req", "route": "negotiate" }, { "type": "http", "direction": "out", "name": "res" }, { "type": "signalRConnectionInfo", "name": "connectionInfo", "hubName": "default", "connectionStringSetting": "AzureSignalRConnectionString", "direction": "in" } ] }
There's no
userId
property in thesignalRConnectionInfo
binding for local development, but you'll add it later to set the user name of a SignalR connection when you deploy the function app to Azure.Close the negotiate/function.json file.
Open negotiate/index.js to view the body of the function.
module.exports = async function (context, req, connectionInfo) { context.res.body = connectionInfo; };
This function takes the SignalR connection information from the input binding and returns it to the client in the HTTP response body. The SignalR client uses this information to connect to the SignalR Service instance.
Create a function to send chat messages
The web app also requires an HTTP API to send chat messages. You'll create an HTTP triggered function named sendMessage
that sends messages to all connected clients using SignalR Service.
From the root project folder, create an HTTP trigger function named
sendMessage
from the template with the command:func new --name sendMessage --template "Http trigger"
To configure bindings for the function, replace the content of sendMessage/function.json with the following code:
{ "disabled": false, "bindings": [ { "authLevel": "anonymous", "type": "httpTrigger", "direction": "in", "name": "req", "route": "messages", "methods": ["post"] }, { "type": "http", "direction": "out", "name": "res" }, { "type": "signalR", "name": "$return", "hubName": "default", "direction": "out" } ] }
Two changes are made to the original file:
- Changes the route to
messages
and restricts the HTTP trigger to thePOST
HTTP method. - Adds a SignalR Service output binding that sends a message returned by the function to all clients connected to a SignalR Service hub named
default
.
- Changes the route to
Replace the content of sendMessage/index.js with the following code:
module.exports = async function (context, req) { const message = req.body; message.sender = req.headers && req.headers['x-ms-client-principal-name'] || ''; let recipientUserId = ''; if (message.recipient) { recipientUserId = message.recipient; message.isPrivate = true; } return { 'userId': recipientUserId, 'target': 'newMessage', 'arguments': [ message ] }; };
This function takes the body from the HTTP request and sends it to clients connected to SignalR Service, invoking a function named
newMessage
on each client.The function can read the sender's identity and can accept a
recipient
value in the message body to allow you to send a message privately to a single user. You'll use these functionalities later in the tutorial.Save the file.
Host the chat client web user interface
The chat application's UI is a simple single-page application (SPA) created with the Vue JavaScript framework using ASP.NET Core SignalR JavaScript client. For simplicity, the function app hosts the web page. In a production environment, you can use Static Web Apps to host the web page.
Create a new folder named content in the root directory of your function project.
In the content folder, create a new file named index.html.
Copy and paste the content of index.html to your file. Save the file.
From the root project folder, create an HTTP trigger function named
index
from the template with the command:func new --name index --template "Http trigger"
Modify the content of
index/index.js
to the following:const fs = require('fs'); module.exports = async function (context, req) { const fileContent = fs.readFileSync('content/index.html', 'utf8'); context.res = { // status: 200, /* Defaults to 200 */ body: fileContent, headers: { 'Content-Type': 'text/html' }, }; }
The function reads the static web page and returns it to the user.
Open index/function.json, change the
authLevel
of the bindings toanonymous
. Now the whole file looks like this:{ "bindings": [ { "authLevel": "anonymous", "type": "httpTrigger", "direction": "in", "name": "req", "methods": ["get", "post"] }, { "type": "http", "direction": "out", "name": "res" } ] }
Now you can test your app locally. Start the function app with the command:
func start
Open http://localhost:7071/api/index in your web browser. You should be able to see a web page as follows:
Enter a message in the chat box and press enter.
The message is displayed on the web page. Because the user name of the SignalR client isn't set, we send all messages as "anonymous".
Deploy to Azure and enable authentication
You have been running the function app and chat application locally. You'll now deploy them to Azure and enable authentication and private messaging in the application.
Configure function app for authentication
So far, the chat app works anonymously. In Azure, you'll use App Service Authentication to authenticate the user. The user ID or username of the authenticated user is passed to the SignalRConnectionInfo
binding to generate connection information authenticated as the user.
Open negotiate/function.json.
Insert a
userId
property to theSignalRConnectionInfo
binding with value{headers.x-ms-client-principal-name}
. This value is a binding expression that sets the user name of the SignalR client to the name of the authenticated user. The binding should now look like this.{ "type": "signalRConnectionInfo", "name": "connectionInfo", "userId": "{headers.x-ms-client-principal-name}", "hubName": "default", "direction": "in" }
Save the file.
Deploy function app to Azure
Deploy the function app to Azure with the following command:
func azure functionapp publish <your-function-app-name> --publish-local-settings
The --publish-local-settings
option publishes your local settings from the local.settings.json file to Azure, so you don't need to configure them in Azure again.
Enable App Service Authentication
Azure Functions supports authentication with Azure Active Directory, Facebook, Twitter, Microsoft account, and Google. You will use Microsoft as the identity provider for this tutorial.
Go to the resource page of your function app on Azure portal.
Select Settings -> Authentication.
Select Add identity provider.
Select Microsoft from the Identity provider list.
Azure Functions supports authentication with Azure Active Directory, Facebook, Twitter, Microsoft account, and Google. For more information about the supported identity providers, see the following articles:
Select Add to complete the settings. An app registration will be created, which associates your identity provider with your function app.
Try the application
Open https://<YOUR-FUNCTION-APP-NAME>.azurewebsites.net/api/index
Select Login to authenticate with your chosen authentication provider.
Send public messages by entering them into the main chat box.
Send private messages by clicking on a username in the chat history. Only the selected recipient will receive these messages.
Congratulations! You've deployed a real-time, serverless chat app!
Clean up resources
To clean up the resources created in this tutorial, delete the resource group using the Azure portal.
Caution
Deleting the resource group deletes all resources contained within it. If the resource group contains resources outside the scope of this tutorial, they will also be deleted.
Next steps
In this tutorial, you learned how to use Azure Functions with Azure SignalR Service. Read more about building real-time serverless applications with SignalR Service bindings for Azure Functions.
Feedback
Submit and view feedback for