If you've ever tried to provision a Logic App (Consumption) end‑to‑end with an API connection for Azure Tables wired to a managed identity, you probably noticed the documentation is – let's say – thin. You quickly end up reverse engineering exported ARM templates or clicking in the portal to see what gets generated. This post documents a repeatable Bicep approach (user‑assigned managed identity + Azure Tables connection) and why (for now) AI is still of limited help for these integration cases.
Why this post (and why AI did not just write it for me)
I like to lean on AI for boring boilerplate. For integration scenarios though, AI still regularly hallucinates subtle but critical details: wrong resource types, outdated API versions, or non‑existent properties inside a Logic App $connections parameter. The training data for niche integration Infrastructure‑as‑Code (IaC) patterns is simply smaller than, say, generic web API samples or CRUD React apps. The result? Confidently wrong suggestions. I had ChatGPT and other models produce:
authType: ManagedIdentitywhile the platform expectsManagedServiceIdentity.- A
managedIdentityobject inside theMicrosoft.Web/connectionsresource (doesn't exist for this scenario; the logic app's own identity is used). - Incorrect
api.idformats (/providers/Microsoft.Web/locations/managedApis/...missing the subscription context) or hardcoded location mismatches. - Mixing Standard (single tenant) and Consumption (multi tenant) concepts (e.g. suggesting built‑in connectors that only apply to Standard).
So we (still) document. Here's a concise working pattern you can adapt.
Scenario
We provision:
- A user‑assigned managed identity (typically you'd reuse an existing one; here we create it for completeness).
- A Storage account plus an Azure Table (data plane resource) to hold run log entities.
- RBAC role assignment granting the identity
Storage Table Data Contributoron the storage account. - An Azure Tables API connection (
azuretables) configured for Managed Identity, using the parameter value set style the portal emits today. - A Logic App (Consumption) referencing that connection via
$connectionswith an explicitconnectionProperties.authentication.identityreference to the user‑assigned identity.
All in one Bicep deployment, idempotent, no portal clicks.
High‑level steps
- Define parameters (company, environment, location, names).
- Create / reference user‑assigned managed identity.
- Create storage account + table.
- Assign
Storage Table Data Contributorrole to the identity. - Create Azure Tables connection (
Microsoft.Web/connections) using the managed identity parameter value set. - Deploy workflow referencing connection + adding authentication block under
$connections. - (Optional) Add outputs or more tables.
Bicep walkthrough
Below is a condensed version of the relevant parts. (Your full file also injects the Logic App definition from a JSON file – a good practice to keep workflow JSON separate.) Focus on: user‑assigned identity creation, role assignment, connection, $connections parameter with authentication block.
The artifacts can be found on github
targetScope = 'resourceGroup'
param companyName string = 'Didago'
@allowed([ 'dev','tst','acc','prd' ])
param environmentName string
param location string = 'westeurope'
param logicAppName string = 'Demo-blog-la-${environmentName}-v001'
// User Assigned Managed Identity (would usually pre-exist)
param userManagedIdentityRg string = '${toLower(companyName)}-blog-rg-${toLower(environmentName)}'
param userManagedIdentityName string = '${toLower(companyName)}-blog-mi-${toLower(environmentName)}'
// Storage dependencies
param logStorageAccountName string = '${toLower(companyName)}log${substring(uniqueString(resourceGroup().id), 0, 10)}${toLower(environmentName)}'
var azureTablesConnectionName = 'azuretables'
// Identity
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: userManagedIdentityName
location: location
}
// Storage + table
resource logStorageAccount 'Microsoft.Storage/storageAccounts@2025-01-01' = {
name: logStorageAccountName
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: { supportsHttpsTrafficOnly: true }
}
resource runLogsTable 'Microsoft.Storage/storageAccounts/tableServices/tables@2025-01-01' = {
name: '${logStorageAccount.name}/default/RunLogs'
}
// RBAC for tables (Storage Table Data Contributor)
resource tableContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: logStorageAccount
name: guid(userAssignedIdentity.id, logStorageAccount.id, 'table-contributor')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')
principalId: userAssignedIdentity.properties.principalId
principalType: 'ServicePrincipal'
}
}
// Azure Tables connection (managed identity flavor)
resource azureTablesConnection 'Microsoft.Web/connections@2016-06-01' = {
name: azureTablesConnectionName
location: location
properties: {
displayName: azureTablesConnectionName
api: {
id: subscriptionResourceId('Microsoft.Web/locations/managedApis', location, 'azuretables')
}
// Newer pattern uses parameterValueSet instead of parameterValues + authType
parameterValueSet: {
name: 'managedIdentityAuth'
values: {}
}
}
}
// Logic App (user-assigned identity & $connections auth block)
resource logicApp 'Microsoft.Logic/workflows@2019-05-01' = {
name: logicAppName
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: { '${userAssignedIdentity.id}': {} }
}
properties: {
definition: json(loadTextContent('../../src/logic-app-1/workflow.json')).definition
parameters: union(
json(loadTextContent('../../src/logic-app-1/workflow.json')).parameters,
{
LogStorageAccountName: {
value: logStorageAccountName
}
'$connections': {
value: {
azuretables: {
connectionId: azureTablesConnection.id
connectionName: azureTablesConnection.name
id: subscriptionResourceId('Microsoft.Web/locations/managedApis', location, 'azuretables')
connectionProperties: {
authentication: {
type: 'ManagedServiceIdentity'
identity: resourceId(userManagedIdentityRg, 'Microsoft.ManagedIdentity/userAssignedIdentities', userManagedIdentityName)
}
}
}
}
}
}
)
}
}
Key points:
- Two parallel patterns exist: older
parameterValues.authType = ManagedServiceIdentityand newerparameterValueSet+ an auth block under$connections→ This connector currently needs the auth in$connectionsfor user‑assigned scenarios. - The identity reference inside
connectionProperties.authentication.identitymust use the resource scope RG of the identity (can differ from the Logic App RG in cross‑RG reuse scenarios).
Switching to a system-assigned identity? Replace the identity block on the workflow with { type: 'SystemAssigned' }, drop connectionProperties.authentication.identity (still accepted but optional), and change the role assignment principal ID accordingly. The connection resource remains unchanged.
You cannot switch an existing connection from key based to MI in place; delete + recreate if you must change auth method.
Where AI tripped up for me
Concrete misfires I saw:
- Replaced the modern
parameterValueSetwith anauthTypeproperty only (worked for some connectors, failed here with user‑assigned identity). - Suggested adding a
managedIdentityobject inside theMicrosoft.Web/connectionsresource (non‑existent; identity context comes from workflow at runtime or$connectionsauth block). - Produced a Standard plan pattern (
connectionRuntimeUrl) instead of the Consumption pattern usinghost.connection.name. - Gave the wrong role GUID (Queue / Blob contributor) which compiles but lacks Table Data permissions → 403 at runtime.
They look plausible, but each is just “off” enough to cost you time.
Final thoughts
Integration IaC still benefits from precise, curated examples. AI accelerates drafting but its weaker training signal for connector internals (like the subtle $connections auth structure) means you still need exported templates, schema docs – or posts like this. Until models ingest more quality integration artifacts, documenting patterns like user‑assigned MI + Azure Tables keeps teams moving. Spot something off or want a Blob / Queue / Key Vault variant? Let me know. Happy provisioning!