Azure SignalR Service Serverless with Managed Identity and Bicep

This post describes how an Azure SignalR Service can be deployed and used through Managed Identity using Bicep. This can be used to provide SignalR capabilities to Azure Functions or WebApps even when behind Azure Frontdoor (still no Websocket support) by leveraging the serverless option of the SignalR Service.

First we create the SignalR Service and set it’s service mode to serverless. Note the way the connection string is constructed in the output section. It does not contain any secrets but instead instructs the SDK to use an Azure Managed Identity for authorization. This way we can also easily use a regular connection string for local debugging using user secrets and without any code changes.

param location string
param tags object
param signalRServiceName string
param signalRServiceSKU string
param allowedOrigins string[]
resource signalRService 'Microsoft.SignalRService/signalR@2023-02-01' = {
name: signalRServiceName
tags: union(tags, {
Purpose: 'SignalR Service for frontend user notifications'
})
location: location
properties: {
features: [
{
flag: 'ServiceMode'
value: 'Serverless'
}
]
cors: {
allowedOrigins: allowedOrigins
}
}
sku: {
name: signalRServiceSKU
}
}
output name string = signalRService.name
output connectionString string = 'Endpoint=https://${signalRService.properties.hostName};AuthType=azure.msi;Version=1.0;'
view raw signalr.bicep hosted with ❤ by GitHub

Next we need to provide our app with the proper permissions to access the SignalR Service. There are different roles available depending on our usecase. With the serverless mode there are two responsibilites for our backend:

  • Creating a negotiation endpoint to provide clients with credentials to the SignalR Service. For this we need the SignalR App Server role.
  • Sending messages via the SignalR REST API that should be relayed to the clients. Here we require the SignalR REST API Owner role.

The following module provides the corresponding role ids necessary for configuring the access control.

param signalrName string
param principalId string
param role ('SignalR App Server' | 'SignalR REST API Owner')
var signalRAppServerRoleDefinitionId = '420fcaa2-552c-430f-98ca-3264be4806c7' // required for authentication / negotiation
var signalRRestApiOwnerRoleDefinitionId = 'fd53cd77-2268-407a-8f46-7e7863d0f521' // required to send messages over REST
var roleId = role == 'SignalR App Server' ? signalRAppServerRoleDefinitionId : signalRRestApiOwnerRoleDefinitionId
resource signalRService 'Microsoft.SignalRService/signalR@2023-02-01' existing = {
name: signalrName
}
resource roleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
scope: subscription()
name: roleId
}
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, principalId, roleDefinition.id, signalrName)
scope: signalRService
properties: {
roleDefinitionId: roleDefinition.id
principalId: principalId
}
}

In the next post we’ll look at how to authenticate against the SignalR Service from ASP.NET Core and and how to retreive the client connection details from the negotiation endpoint.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.