Skip to content

Latest commit

 

History

History
600 lines (431 loc) · 24.2 KB

signalr-tutorial-authenticate-azure-functions.md

File metadata and controls

600 lines (431 loc) · 24.2 KB
titledescriptionauthorms.servicems.topicms.datems.authorms.devlangms.custom
Tutorial: Authentication with Azure Functions - Azure SignalR Service
In this tutorial, you learn how to authenticate Azure SignalR Service clients for Azure Functions binding.
Y-Sindo
azure-signalr-service
tutorial
03/18/2024
zityang
javascript

Tutorial: Azure SignalR Service authentication with Azure Functions

In this step-by-step tutorial, you build a chat room with authentication and private messaging by using these technologies:

Note

You can get the code mentioned in this article from GitHub.

Note

You can get the code mentioned in this article from GitHub.


[!INCLUDE Connection string security]

Prerequisites

Having issues? Let us know.

Create essential resources on Azure

Create an Azure SignalR Service resource

Your application will access an Azure SignalR Service instance. Use the following steps to create an Azure SignalR Service instance by using the Azure portal:

  1. In the Azure portal, select the Create a resource (+) button.

  2. Search for SignalR Service and select it.

  3. Select Create.

  4. Enter the following information.

    NameValue
    Resource groupCreate a new resource group with a unique name.
    Resource nameEnter a unique name for the Azure SignalR Service instance.
    RegionSelect a region close to you.
    Pricing TierSelect Free.
    Service modeSelect Serverless.
  5. Select Review + Create.

  6. Select Create.

Having issues? Let us know.

Create an Azure function app and an Azure storage account

  1. From the home page in the Azure portal, select Create a resource (+).

  2. Search for Function App and select it.

  3. Select Create.

  4. Enter the following information.

    NameValue
    Resource groupUse the same resource group with your Azure SignalR Service instance.
    Function App nameEnter a unique name for the function app.
    Runtime stackSelect Node.js.
    RegionSelect a region close to you.
  5. By default, a new Azure storage account is 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 the Hosting tab to choose an account.

  6. Select Review + Create, and then select Create.

Create an Azure Functions project locally

Initialize a function app

  1. From a command line, create a root folder for your project and change to the folder.

  2. Run the following command in your terminal to create a new JavaScript Functions project:

func init --worker-runtime node --language javascript --name my-app --model V4
func init --worker-runtime node --language javascript --name my-app --model V3

By default, the generated project includes a host.json file that contains the extension bundles that include the SignalR extension. For more information about extension bundles, see Register Azure Functions binding extensions.

Configure application settings

When you run and debug the Azure Functions runtime locally, the function app reads application settings from local.settings.json. Update this file with the connection strings of the Azure SignalR Service instance and the storage account that you created earlier.

[!INCLUDE Connection string security comment]

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>" } }

In the preceding code:

  • Enter the Azure SignalR Service connection string into the AzureSignalRConnectionString setting.

    To get the string, go to your Azure SignalR Service instance 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.

    To get the string, go 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.

Having issues? Let us know.

Create a function to authenticate users to Azure SignalR Service

When the chat app first opens in the browser, it requires valid connection credentials to connect to Azure SignalR Service. Create an HTTP trigger function named negotiate in your function app to return this connection information.

Note

This function must be named negotiate because the SignalR client requires an endpoint that ends in /negotiate.

  1. From the root project folder, create the negotiate function from a built-in template by using the following command:

    func new --template "HTTP trigger" --name negotiate
  2. Open src/functions/negotiate.js, update the content as follows:

    const{ app, input }=require('@azure/functions');constinputSignalR=input.generic({type: 'signalRConnectionInfo',name: 'connectionInfo',hubName: 'default',connectionStringSetting: 'AzureSignalRConnectionString',});app.post('negotiate',{authLevel: 'anonymous',handler: (request,context)=>{return{body: JSON.stringify(context.extraInputs.get(inputSignalR))}},route: 'negotiate',extraInputs: [inputSignalR],});

    The function contains an HTTP trigger binding to receive requests from SignalR clients. The function also contains a SignalR input binding to generate valid credentials for a client to connect to an Azure SignalR Service hub named default.

    This function takes the SignalR connection information from the input binding and returns it to the client in the HTTP response body..

    There's no userId property in the signalRConnectionInfo binding for local development. You'll add it later to set the username of a SignalR connection when you deploy the function app to Azure.

  1. From the root project folder, create the negotiate function from a built-in template by using the following command:

    func new --template "SignalR negotiate HTTP trigger" --name negotiate
  2. Open negotiate/function.json to view the function binding configuration.

    The function contains an HTTP trigger binding to receive requests from SignalR clients. The function also contains 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 the signalRConnectionInfo binding for local development. You'll add it later to set the username of a SignalR connection when you deploy the function app to Azure.

  3. Close the negotiate/function.json file.

  4. Open negotiate/index.js to view the body of the function:

    module.exports=asyncfunction(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 Azure SignalR Service instance.


Having issues? Let us know.

Create a function to send chat messages

The web app also requires an HTTP API to send chat messages. Create an HTTP trigger function that sends messages to all connected clients that use Azure SignalR Service:

  1. From the root project folder, create an HTTP trigger function named sendMessage from the template by using the following command:

    func new --name sendMessage --template "Http trigger"
  2. Open the src/functions/sendMessage.js file, update the content as follows:

    const{ app, output }=require('@azure/functions');constsignalR=output.generic({type: 'signalR',name: 'signalR',hubName: 'default',connectionStringSetting: 'AzureSignalRConnectionString',});app.http('messages',{methods: ['POST'],authLevel: 'anonymous',extraOutputs: [signalR],handler: async(request,context)=>{constmessage=awaitrequest.json();message.sender=request.headers&&request.headers.get('x-ms-client-principal-name')||'';letrecipientUserId='';if(message.recipient){recipientUserId=message.recipient;message.isPrivate=true;}context.extraOutputs.set(signalR,{'userId': recipientUserId,'target': 'newMessage','arguments': [message]});}});

    The function contains an HTTP trigger and a SignalR output binding. It takes the body from the HTTP request and sends it to clients connected to Azure SignalR Service. It invokes 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.

  3. Save the file.

  1. From the root project folder, create an HTTP trigger function named sendMessage from the template by using the following command:

    func new --name sendMessage --template "Http trigger"
  2. 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" } ] }

    The preceding code makes two changes to the original file:

    • It changes the route to messages and restricts the HTTP trigger to the POST HTTP method.
    • It adds an Azure SignalR Service output binding that sends a message returned by the function to all clients connected to an Azure SignalR Service hub named default.
  3. Replace the content of sendMessage/index.js with the following code:

    module.exports=asyncfunction(context,req){constmessage=req.body;message.sender=(req.headers&&req.headers["x-ms-client-principal-name"])||"";letrecipientUserId="";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 Azure SignalR Service. It invokes 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.

  4. Save the file.


Having issues? Let us know.

Host the chat client's web user interface

The chat application's UI is a simple single-page application (SPA) created with the Vue JavaScript framework by using the ASP.NET Core SignalR JavaScript client. For simplicity, the function app hosts the webpage. In a production environment, you can use Static Web Apps to host the webpage.

  1. Create a file named index.html in the root directory of your function project.

  2. Copy and paste the content of index.html to your file. Save the file.

  3. From the root project folder, create an HTTP trigger function named index from the template by using this command:

    func new --name index --template "Http trigger"
  4. Modify the content of src/functions/index.js to the following code:

    const{ app }=require('@azure/functions');const{ readFile }=require('fs/promises');app.http('index',{methods: ['GET'],authLevel: 'anonymous',handler: async(context)=>{constcontent=awaitreadFile('index.html','utf8',(err,data)=>{if(err){context.err(err)return}});return{status: 200,headers: {'Content-Type': 'text/html'},body: content,};}});

    The function reads the static webpage and returns it to the user.

  5. Test your app locally. Start the function app by using this command:

    func start
  6. Open http://localhost:7071/api/index in your web browser. A chat webpage should appear.

    :::image type="content" source="./media/signalr-tutorial-authenticate-azure-functions/local-chat-client-ui.png" alt-text="Screenshot of a web user interface for a local chat client.":::

  7. Enter a message in the chat box.

    After you select the Enter key, the message appears on the webpage. Because the username of the SignalR client isn't set, you're sending all messages anonymously.

  1. Create a folder named content in the root directory of your function project.

  2. In the content folder, create a file named index.html.

  3. Copy and paste the content of index.html to your file. Save the file.

  4. From the root project folder, create an HTTP trigger function named index from the template by using this command:

    func new --name index --template "Http trigger"
  5. Modify the content of index/index.js to the following code:

    constfs=require("fs");module.exports=asyncfunction(context,req){constfileContent=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 webpage and returns it to the user.

  6. Open index/function.json, and change the authLevel value of the bindings to anonymous. Now the whole file looks like this example:

    { "bindings": [ { "authLevel": "anonymous", "type": "httpTrigger", "direction": "in", "name": "req", "methods": ["get", "post"] }, { "type": "http", "direction": "out", "name": "res" } ] }
  7. Test your app locally. Start the function app by using this command:

    func start
  8. Open http://localhost:7071/api/index in your web browser. A chat webpage should appear.

    :::image type="content" source="./media/signalr-tutorial-authenticate-azure-functions/local-chat-client-ui.png" alt-text="Screenshot of a web user interface for a local chat client.":::

  9. Enter a message in the chat box.

    After you select the Enter key, the message appears on the webpage. Because the username of the SignalR client isn't set, you're sending all messages anonymously.


Having issues? Let us know.

Deploy to Azure and enable authentication

You've been running the function app and chat app locally. Now, deploy them to Azure and enable authentication and private messaging.

Configure the 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.

  1. Open src/functions/negotiate.js file.

  2. Insert a userId property into the inputSignalR binding with the value {headers.x-ms-client-principal-name}. This value is a binding expression that sets the username of the SignalR client to the name of the authenticated user. The binding should now look like this example:

    constinputSignalR=input.generic({type: 'signalRConnectionInfo',name: 'connectionInfo',hubName: 'default',connectionStringSetting: 'AzureSignalRConnectionString',userId: '{headers.x-ms-client-principal-name}'});
  3. Save the file.

  1. Open negotiate/function.json.

  2. Insert a userId property into the SignalRConnectionInfo binding with the value {headers.x-ms-client-principal-name}. This value is a binding expression that sets the username of the SignalR client to the name of the authenticated user. The binding should now look like this example:

    { "type": "signalRConnectionInfo", "name": "connectionInfo", "userId": "{headers.x-ms-client-principal-name}", "hubName": "default", "direction": "in" }
  3. Save the file.


Deploy the function app to Azure

Deploy the function app to Azure by using 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 Microsoft Entra ID, Facebook, X, Microsoft account, and Google. You'll use Microsoft as the identity provider for this tutorial.

  1. In the Azure portal, go to the resource page of your function app.

  2. Select Settings > Authentication.

  3. Select Add identity provider.

    :::image type="content" source="./media/signalr-tutorial-authenticate-azure-functions/function-app-authentication.png" alt-text="Screenshot of the function app Authentication page and the button for adding an identity provider.":::

  4. In the Identity provider list, select Microsoft. Then select Add.

    :::image type="content" source="media/signalr-tutorial-authenticate-azure-functions/function-app-select-identity-provider.png" alt-text="Screenshot of the page for adding an identity provider.":::

The completed settings create an app registration that associates your identity provider with your function app.

For more information about the supported identity providers, see the following articles:

Try the application

  1. Open https://<YOUR-FUNCTION-APP-NAME>.azurewebsites.net/api/index.
  2. Select Login to authenticate with your chosen authentication provider.
  3. Send public messages by entering them in the main chat box.
  4. Send private messages by selecting a username in the chat history. Only the selected recipient receives these messages.

:::image type="content" source="./media/signalr-tutorial-authenticate-azure-functions/online-chat-client-ui.png" alt-text="Screenshot of an authenticated online client chat app.":::

Congratulations! You deployed a real-time, serverless chat app.

Having issues? Let us know.

Clean up resources

To clean up the resources that you created in this tutorial, delete the resource group by using the Azure portal.

Caution

Deleting the resource group deletes all the resources that it contains. If the resource group contains resources outside the scope of this tutorial, they're also deleted.

Having issues? Let us know.

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 Azure SignalR Service bindings for Azure Functions:

[!div class="nextstepaction"] Real-time apps with Azure SignalR Service and Azure Functions

Having issues? Let us know.

close