Entra ID — App Registrations at Scale
Enterprise Identity Management for Integration Workloads
Introduction
Managing application identities at enterprise scale requires more than just creating app registrations in Entra ID (formerly Azure AD). You need a structured approach to service principals, managed identities, certificate lifecycle management, and role-based access control. When your integration landscape spans dozens of Functions, Logic Apps, and API connections, having identity management at scale becomes critical for security and operational efficiency.
This comprehensive guide covers:
- Enterprise app registration strategy — Organizing at scale
- Managed identities — Elimination of secrets
- Service principal management — Automation and governance
- Certificate lifecycle — Rotation and renewal
- Access control patterns — RBAC for integration services
Enterprise Organization
Application Hierarchy
┌─────────────────────────────────────────────────────────────────────┐
│ ENTRA ID ORGANIZATION │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Tenant │
│ └── Enterprise Applications │
│ │ │
│ ├── Production │
│ │ ├── Integration Services │
│ │ │ ├── order-processing-api │
│ │ │ ├── customer-sync-service │
│ │ │ └── inventory-webhook │
│ │ │ │
│ │ ├── External Integrations │
│ │ │ ├── sap-connection │
│ │ │ ├── salesforce-integration │
│ │ │ └── partner-api-access │
│ │ │ │
│ │ └── Internal Tools │
│ │ ├── monitoring-dashboard │
│ │ └── admin-portal │
│ │ │
│ ├── Staging (mirror production structure) │
│ │ │
│ └── Development (ephemeral, auto-cleanup) │
│ │
└─────────────────────────────────────────────────────────────────────┘
Naming Conventions
{
"namingConventions": {
"appRegistration": {
"pattern": "{environment}-{service}-{purpose}",
"examples": [
"prod-functions-orders-api",
"staging-logicapps-customer-sync",
"dev-servicebus-sender"
]
},
"servicePrincipal": {
"pattern": "sp-{app-registration-name}",
"autoProvision": true
},
"managedIdentity": {
"pattern": "{resource-type}-{environment}-{name}",
"examples": [
"func-prod-order-processor",
"la-staging-data-migration",
"vm-prod-integration-runner"
]
},
"secretCredential": {
"pattern": "{app}-{purpose}-{rotation-date}",
"description": "Include rotation date in description"
}
}
}
Managed Identities
Azure Function with Managed Identity
// Using Managed Identity to access Service Bus
public class ManagedIdentityFunction
{
private readonly ServiceBusClient _serviceBusClient;
public ManagedIdentityFunction(IConfiguration config)
{
// No connection string needed - uses managed identity
_serviceBusClient = new ServiceBusClient(
"mynamespace.servicebus.windows.net",
new DefaultAzureCredential());
}
[Function("ProcessOrders")]
public async Task Run(
[ServiceBusTrigger("orders", Connection = "ServiceBusEndpoint")]
ServiceBusReceivedMessage message)
{
// Process message - automatically uses managed identity
var order = JsonSerializer.Deserialize<Order>(message.Body.ToString());
await _orderService.ProcessAsync(order);
}
}
// Configuration (no secrets needed in configuration!)
// appsettings.json
{
"Values": {
"ServiceBusEndpoint": "https://mynamespace.servicebus.windows.net"
},
"ConnectionStrings": {}
}
Managed Identity for Multiple Resources
public class MultiResourceManagedIdentity
{
private readonly TokenCredential _credential;
public MultiResourceManagedIdentity()
{
_credential = new DefaultAzureCredential(
new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = "optional-specific-mi-id"
});
}
public ServiceBusClient CreateServiceBusClient(string namespace_)
{
return new ServiceBusClient($"{namespace_}.servicebus.windows.net",
_credential);
}
public CosmosClient CreateCosmosClient(string accountName)
{
return new CosmosClient(
$"https://{accountName}.documents.azure.com",
_credential);
}
public BlobClient CreateBlobClient(string accountName, string container, string blob)
{
return new BlobClient(
new Uri($"https://{accountName}.blob.core.windows.net/{container}/{blob}"),
_credential);
}
public KeyVaultClient CreateKeyVaultClient(string vaultUrl)
{
return new KeyVaultClient(
new KeyVaultCredentialHandler(_credential));
}
}
Enabling Managed Identity
# Enable managed identity on Function App
az functionapp identity assign \
--name my-function-app \
--resource-group my-rg \
--system-assigned
# Enable on Logic Apps (via ARM)
az deployment group create \
--resource-group my-rg \
--template-file logic-app-with-mi.json
# Verify managed identity
az functionapp show \
--name my-function-app \
--resource-group my-rg \
--query identity
# Grant access to resources
# Grant Service Bus Sender role
az role assignment create \
--assignee <managed-identity-principal-id> \
--role "Azure Service Bus Data Sender" \
--scope /subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.ServiceBus/namespaces/mynamespace
# Grant Key Vault access
az keyvault set-policy \
--name my-keyvault \
--object-id <managed-identity-principal-id> \
--secret-permissions get list
Service Principal Management
Automated Service Principal Lifecycle
public class ServicePrincipalManager
{
public async Task<Application> CreateApplicationAsync(ApplicationRequest request)
{
var app = new Application
{
DisplayName = request.DisplayName,
Description = request.Description,
SignInAudience = request.IsInternal
? "AzureADMultipleOrgs"
: "AzureADMyOrg",
RequiredResourceAccess = request.RequiredPermissions
.Select(p => new RequiredResourceAccess
{
ResourceAppId = p.ResourceAppId,
ResourceAccess = p.Accesses
}).ToList()
};
return await _graphClient.Applications
.Request()
.AddAsync(app);
}
public async Task CreateServicePrincipalAsync(string appId)
{
var sp = new ServicePrincipal
{
AppId = appId,
AccountEnabled = true
};
await _graphClient.ServicePrincipals
.Request()
.AddAsync(sp);
}
public async Task RotateCredentialAsync(string appId, int validityDays = 365)
{
// Add new password credential
var passwordCredential = new PasswordCredential
{
DisplayName = $"Rotated {DateTime.UtcNow:yyyy-MM-dd}",
EndDateTime = DateTime.UtcNow.AddDays(validityDays)
};
await _graphClient.Applications[appId]
.AddPassword(passwordCredential)
.Request()
.PostAsync();
// Optionally: Remove old credentials older than X days
// This would require tracking credential creation dates
}
}
GitHub Actions for App Registration
name: Manage App Registration
on:
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: true
type: choice
options:
- create
- rotate-secret
- disable
jobs:
manage-app:
runs-on: ubuntu-latest
steps:
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Create App Registration
if: github.event.inputs.action == 'create'
run: |
az ad app create \
--display-name "${{ vars.APP_NAME }}" \
--identifier-uris "api://${{ vars.APP_NAME }}" \
--required-resource-access @manifest.json
- name: Rotate Secret
if: github.event.inputs.action == 'rotate-secret'
run: |
# Generate new client secret
az ad app credential reset \
--id "${{ vars.APP_ID }}" \
--append
# Update Key Vault with new secret
az keyvault secret set \
--vault-name "${{ vars.KEY_VAULT }}" \
--name "${{ vars.SECRET_NAME }}" \
--value "$AZURE_CLIENT_SECRET"
- name: Disable App
if: github.event.inputs.action == 'disable'
run: |
az ad app update \
--id "${{ vars.APP_ID }}" \
--set availableToOtherTenants=false
Certificate Management
Certificate Lifecycle
# Create certificate with Key Vault
az keyvault certificate create \
--vault-name my-keyvault \
--name integration-cert \
--policy @policy.json
# policy.json
{
"issuerParameters": {
"certificateType": "Self-Signed",
"name": "Self"
},
"keyProperties": {
"exportable": true,
"keySize": 2048,
"keyType": "RSA",
"reuseKey": false
},
"lifetimeActions": [
{
"trigger": {
"daysBeforeExpiry": 30
},
"action": {
"actionType": "Notify"
}
},
{
"trigger": {
"daysBeforeExpiry": 7
},
"action": {
"actionType": "AutoRenew"
}
}
],
"secretProperties": {
"contentType": "application/x-pkcs12"
}
}
Certificate Rotation Automation
public class CertificateRotationService
{
private readonly KeyVaultClient _keyVaultClient;
public async Task RotateCertificateAsync(string appRegObjectId)
{
// Get current certificate from Key Vault
var currentCert = await _keyVaultClient
.GetSecretAsync("keyvault-url", "integration-cert");
// Create new certificate
var newCert = await _keyVaultClient
.CreateCertificateAsync("keyvault-url", new CertificatePolicy
{
IssuerParameters = new IssuerParameters("Self"),
KeyProperties = new KeyProperties(keySize: 2048),
LifetimeActions = new[]
{
new LifetimeAction
{
Action = CertificateActionType.AutoRenew,
Trigger = new Trigger
{
DaysBeforeExpiry = 30
}
}
}
});
// Update Application manifest with new certificate
var updatedCredential = new KeyCredential
{
KeyId = Guid.NewGuid().ToString(),
DisplayName = $"Certificate {DateTime.UtcNow:yyyy-MM}",
StartDateTime = DateTime.UtcNow,
EndDateTime = DateTime.UtcNow.AddYears(1),
Type = "AsymmetricX509Cert",
Usage = "Verify"
};
await _graphClient.Applications[appRegObjectId]
.AddKey(updatedCredential)
.Request()
.PostAsync();
// Store new thumbprint for reference
await _configStore.SetAsync(
$"cert-thumbprint-{DateTime.UtcNow:yyyyMM}",
newCert.Thumbprint);
}
}
Access Control Patterns
Role-Based Access for Integration Services
{
"rbacModel": {
"integrationDeveloper": {
"description": "Can deploy and manage integration resources",
"assignableScopes": [
"/subscriptions/xxx/resourceGroups/rg-integration"
],
"permissions": [
"Microsoft.Web/sites/read",
"Microsoft.Web/sites/write",
"Microsoft.Web/functions/read",
"Microsoft.Web/functions/write",
"Microsoft.ServiceBus/namespaces/queues/read",
"Microsoft.ServiceBus/namespaces/queues/write"
]
},
"integrationOperator": {
"description": "Can monitor and operate integration flows",
"assignableScopes": [
"/subscriptions/xxx/resourceGroups/rg-integration"
],
"permissions": [
"Microsoft.Web/sites/read",
"Microsoft.ServiceBus/namespaces/queues/read",
"Microsoft.ServiceBus/namespaces/topics/read",
"Microsoft.Insights/metrics/read"
]
},
"integrationViewer": {
"description": "Read-only access to integration resources",
"assignableScopes": [
"/subscriptions/xxx"
],
"permissions": [
"Microsoft.Web/sites/read",
"Microsoft.ServiceBus/namespaces/read",
"Microsoft.Insights/components/read"
]
}
}
}
Custom Role Definition
az role definition create \
--role-definition '{
"Name": "Integration Developer",
"IsCustom": true,
"Description": "Can develop and deploy integration solutions",
"Actions": [
"Microsoft.Web/sites/read",
"Microsoft.Web/sites/write",
"Microsoft.Web/sites/extensions/read",
"Microsoft.Web/sites/extensions/write",
"Microsoft.Web/hostingEnvironments/read",
"Microsoft.ServiceBus/namespaces/read",
"Microsoft.ServiceBus/namespaces/queues/*",
"Microsoft.ServiceBus/namespaces/topics/*",
"Microsoft.Storage/storageAccounts/blobServices/containers/read",
"Microsoft.KeyVault/vaults/secrets/read",
"Microsoft.KeyVault/vaults/secrets/write"
],
"NotActions": [],
"DataActions": [],
"NotDataActions": [],
"AssignableScopes": [
"/subscriptions/xxx/resourceGroups/rg-integration"
]
}'
Best Practices
Implementation Checklist
| Practice | Description |
|---|---|
| Use managed identities | Avoid secrets where possible |
| Implement naming conventions | Consistent naming enables management |
| Automate credential rotation | Don't rely on manual processes |
| Use groups for assignments | Assign roles to groups, not individuals |
| Audit access regularly | Quarterly access reviews |
| Document purpose | Every app registration should have clear purpose |
Security Considerations
┌─────────────────────────────────────────────────────────────────────┐
│ IDENTITY SECURITY CHECKLIST │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ✓ Disable legacy authentication (basic auth) │
│ ✓ Require MFA for all admin accounts │
│ ✓ Use conditional access policies │
│ ✓ Rotate credentials automatically (max 1 year) │
│ ✓ Monitor sign-in logs for anomalies │
│ ✓ Use service principals for automation │
│ ✓ Grant least-privilege permissions │
│ ✓ Enable identity protection │
│ │
└─────────────────────────────────────────────────────────────────────┘
Related Topics
- Zero Trust Networking — Identity-based security
- Key Vault Governance — Secrets management
- Compliance as Code — Policy enforcement
Azure Integration Hub - Architect Level Security Architecture & Zero Trust