Easy way to set Azure RBAC roles in Bicep

When deploying resources in Azure using Bicep, occasionally you will have to assign rights to a user or principal to perform certain actions. For example, authorizing an app service to access a storage account.

Initially you would create something like this:

// Assume we have an app service with a System Assigned managed service identity
var principalId = appService.identity.principalId;

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' existing = {
    name: 'some-existing-storage-account'
}

resource roleAuthorization 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
    name: guid(storageAccount.id, resourceGroup().id, principalId)
    scope: storageAccount
    properties: {
        principalId: principalId
        roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Storage Blob Data Contributor
    }
}

I came up with the following Bicep module which shows a nice way to hide the nasty details such as the role guids in a module.

param storageAccountName string
param principalId string

@allowed[
    'Device'
    'ForeignGroup'
    'Group'
    'ServicePrincipal'
    'User'
    ''
]
param principalType string = ''

@allowed([
    'Storage Blob Data Contributor'
    'Storage Blob Data Reader'
])
param roleDefinition string

var roles = {
    // See https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for these mappings and more.
    'Storage Blob Data Contributor': '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe'
    'Storage Blob Data Reader': '/providers/Microsoft.Authorization/roleDefinitions/2a2b9908-6ea1-4ae2-8e65-a410df84e7d1'
}

var roleDefinitionId = roles[roleDefinition]

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' existing = {
    name: storageAccountName
}

resource roleAuthorization 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
    // Generate a unique but deterministic resource name
    name: guid('storage-rbac', storageAccount.id, resourceGroup().id, principalId, roleDefinitionId)
    scope: storageAccount
    properties: {
        principalId: principalId
        roleDefinitionId: roleDefinitionId
        principalType: empty(principalType) ? null : principalType
    }
}

This makes the Bicep files a lot more readable, especially when you have to assign roles more often.

Creating a module to do this also has the advantage that you can change the scope, for example when the storage account is part of a different resource group.

Cleaned up initial example:

// Assume we have an app service with a System Assigned managed service identity
var principalId = appService.identity.principalId;

// I used 'storageAuth.bicep' as the file name, but doesn't matter.
module roleAuthorization 'storageAuth.bicep' = {
    name: 'roleAuthorization'
    properties: {
        principalId: principalId
        storageAccountName: 'some-existing-storage-account'
        roleDefinition: 'Storage Blob Data Contributor'
    }
}

I hope someone has some use for this as well.

Update 18-07-2023: Updated to include principalType in template.