PL400_Azure_Managed_Identities_for_Dataverse_and_Power_Platform
To implement Azure Managed Identities for Dataverse and Power Platform, we analyze the architectural patterns of system-assigned and user-assigned managed identities, focusing on Federated Identity Credentials (FIC) version 2. We map out the step-by-step configuration of Dataverse application users, plugin registration, and token exchange using IManagedIdentityService. Finally, we establish robust error handling, ALM pipelines, and troubleshooting matrices to resolve common authentication failures like AADSTS700213.
Azure Managed Identities for Dataverse and Power Platform
1. Conceptual Foundation
Modern enterprise cloud architectures demand the elimination of static credentials, such as client secrets and certificates, from application code and configuration files. Hardcoded secrets represent a significant attack surface, introducing risks of accidental exposure in source control, administrative overhead during rotation cycles, and service disruptions when credentials expire.
Microsoft Power Platform and Azure address this vulnerability through Power Platform Managed Identities and Federated Identity Credentials (FIC). This framework allows Dataverse plug-ins, custom workflow activities, and other platform components to securely authenticate to Azure resources (such as Azure Key Vault, Azure SQL, and Azure Storage) without storing, managing, or rotating credentials.
The Core Mechanics of Workload Identity Federation
Power Platform Managed Identity is built on Workload Identity Federation, which leverages Microsoft Entra ID (formerly Azure Active Directory) to establish a trust relationship between the Power Platform execution environment and your Azure resources. Instead of using a client secret or certificate to request an access token directly from Entra ID, the platform uses a cryptographic token exchange protocol.
+-------------------------------------------------------------------------------------------------+
| Dataverse Sandbox Environment |
| |
| +------------------+ 1. Request Token +-------------------------------------------+ |
| | Plug-in Assembly | -------------------------> | IManagedIdentityService (Runtime Sidecar) | |
| +------------------+ +-------------------------------------------+ |
+-------------------------------------------------------- | --------------------------------------+
| 2. Request Assertion (Signed by PP)
v
+--------------+
| Microsoft |
| Entra ID |
+--------------+
| 3. Validate Assertion via FIC
| 4. Issue Scoped Access Token
v
+--------------+
| Target App |
| Registration |
+--------------+
The authentication flow operates through the following phases:
- Execution Context Initiation: A Dataverse plug-in executes within the isolated sandbox worker process. The developer requests an access token using the platform-provided
IManagedIdentityServiceinterface. - Assertion Generation: The Dataverse runtime requests a federated assertion token from the internal Power Platform identity provider. This assertion is cryptographically signed by a Microsoft-managed certificate specific to the Power Platform region and environment.
- Token Exchange (OIDC Federation): The runtime sends this assertion to the Microsoft Entra ID token endpoint (
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token). The request specifies the target User-Assigned Managed Identity (UAMI) or App Registration client ID. - Federated Credential Validation: Entra ID intercepts the request and evaluates the configured Federated Identity Credential (FIC) on the target UAMI or App Registration. It verifies that:
- The Issuer matches the Power Platform identity provider URL.
- The Subject Identifier matches the exact runtime context of the calling plug-in (including the environment ID, assembly/package ID, and certificate hashes).
- Token Issuance: Once validated, Entra ID issues an OAuth 2.0 access token scoped to the requested Azure resource (e.g.,
https://vault.azure.net/.default). The plug-in uses this token to perform authorized operations against the target resource.
System-Assigned vs. User-Assigned Managed Identities
Understanding the distinction between System-Assigned Managed Identities (SAMI) and User-Assigned Managed Identities (UAMI) is critical for designing secure integrations:
- System-Assigned Managed Identities (SAMI): Tied directly to the lifecycle of a single Azure resource (e.g., an Azure Function or an Azure Synapse Link profile). When the resource is deleted, Azure automatically cleans up the identity and its associated role assignments in Entra ID. In Power Platform, SAMIs are utilized for platform-level integrations, such as Azure Synapse Link for Dataverse or Microsoft Purview scanning.
- User-Assigned Managed Identities (UAMI): Created as standalone Azure resources in an Azure subscription. A UAMI can be shared across multiple resources and environments, and its lifecycle is managed independently. For custom Dataverse plug-in development, UAMIs (or custom App Registrations) are the standard because they allow developers to pre-provision identities, configure FICs, and bind them to specific plug-in assemblies or packages.
Federated Identity Credentials (FIC) Versions
Microsoft supports two versions of Federated Identity Credentials for Power Platform components:
- Version 1 (CN-Based): This legacy version constructs the subject identifier using the certificate's Common Name (CN). It is highly sensitive to formatting and fails if the CN contains non-ASCII characters (resulting in
AADSTS70050) or commas (resulting inAADSTS700213). - Version 2 (Hash-Based - Recommended): Version 2 computes a fixed-length, ASCII-only subject identifier by taking the SHA-256 hash of the certificate's full Distinguished Name (DN) strings (both Issuer and Subject) and encoding them as URL-safe Base64. This version is robust, supports certificates with special characters, and is the standard for all new deployments.
2. Architecture & Decision Matrix
When designing integrations between Dataverse and external Azure resources, architects must select the appropriate execution context. The table below compares the four primary patterns:
| Architectural Dimension | Power Platform Managed Identity (Plug-ins) | Azure Functions (with Managed Identity) | Power Automate (Cloud Flows) | Power Apps Component Framework (PCF) |
|---|---|---|---|---|
| Complexity | Medium (Requires assembly signing and FIC configuration) | High (Requires hosting, deployment pipelines, and API design) | Low (Drag-and-drop connectors) | High (Client-side TypeScript, requires backend proxy for security) |
| Scalability | High (Executes within Dataverse sandbox scale limits) | Extremely High (Serverless auto-scaling) | Medium (Subject to API request limits and throttling) | Low (Bound by client browser resources) |
| Execution Context | Synchronous or Asynchronous (Dataverse Pipeline) | Asynchronous (Webhooks) or Synchronous (via Custom API) | Asynchronous (Event-driven) | Client-side (User browser context) |
| Offline Support | No | No | No | Yes (via local caching, but authentication requires connectivity) |
| Licensing | Standard Power Apps/Dynamics 365 licenses | Azure consumption costs + Dataverse API call limits | Power Automate Premium / Pay-as-you-go | Bound by host app licensing |
| PL-400 Exam Relevance | Extremely High (Core focus on secure, credential-free integration) | High (Integration patterns) | Medium (Cloud flow extensibility) | High (Client-side development) |
Architectural Recommendation
- Use Power Platform Managed Identity with Dataverse Plug-ins when you require synchronous, low-latency execution within the Dataverse transaction boundary (e.g., retrieving an encryption key from Key Vault to decrypt a field value during the
RetrieveorCreatestage). - Use Azure Functions when the integration requires long-running processes (>2 minutes), heavy computational workloads, or third-party libraries incompatible with the Dataverse sandbox.
- Use Power Automate for asynchronous, orchestrational integrations where low-code maintainability is prioritized over execution speed.
- Use PCF only for client-side UI enhancements, ensuring that no direct secrets are accessed from the client side.
3. Step-by-Step Implementation Guide
This guide walks through configuring a User-Assigned Managed Identity (UAMI) to allow a signed Dataverse plug-in to securely retrieve secrets from Azure Key Vault using FIC Version 2.
Step 1: Create the User-Assigned Managed Identity in Azure
First, provision the UAMI in your Azure subscription.
- Sign in to the Azure Portal (
https://portal.azure.com). - Search for Managed Identities and select Create.
- Configure the following settings:
- Subscription: Select your target subscription.
- Resource Group: Select or create a resource group (e.g.,
rg-powerplatform-prod). - Region: Select the region matching your Dataverse environment.
- Name: Enter a descriptive name (e.g.,
uami-dataverse-plugins-prod).
- Select Review + create, then Create.
- Once deployed, navigate to the resource and copy the Subscription ID, Resource Group Name, Client ID, and Tenant ID from the Overview blade.
Step 2: Generate a Signing Certificate and Sign the Plug-in Assembly
Dataverse requires plug-in assemblies to be cryptographically signed with a strong name key file (.snk) or a certificate (.pfx) to establish identity. For production, use a certificate issued by a trusted Certificate Authority (CA). For development, generate a self-signed certificate.
Run the following PowerShell script to generate a self-signed certificate and export it:
# Generate a self-signed certificate for plug-in signing
$cert = New-SelfSignedCertificate -Subject "CN=DataversePluginSigning" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyExportPolicy Exportable `
-KeySpec Signature `
-KeyLength 2048 `
-HashAlgorithm SHA256
# Export the certificate as a PFX (with private key) for signing
$inputPassword = ConvertTo-SecureString -String "StrongPassword123!" -Force -AsPlainText
Export-PfxCertificate -Cert $cert `
-FilePath "C:\Certs\DataversePluginSigning.pfx" `
-Password $inputPassword
# Export the public key (.cer) for hashing
Export-Certificate -Cert $cert -FilePath "C:\Certs\DataversePluginSigning.cer"
To sign your compiled assembly using SignTool.exe (included in the Windows SDK):
signtool sign /f "C:\Certs\DataversePluginSigning.pfx" /p "StrongPassword123!" /t http://timestamp.digicert.com /fd SHA256 "C:\Source\MyPlugin\bin\Release\MyPluginAssembly.dll"
Step 3: Register the Plug-in Assembly in Dataverse
Using the Plug-in Registration Tool (PRT):
- Open the PRT and connect to your target Dataverse environment.
- Select Register -> Register New Assembly.
- Browse to your signed assembly (
MyPluginAssembly.dll). - Ensure Sandbox isolation mode is selected.
- Select Register Selected Plugins.
- Once registered, select the assembly in the tree view and copy its Assembly ID (GUID) from the properties pane.
Step 4: Compute the Version 2 Issuer and Subject Hashes
To configure the Federated Identity Credential, you must compute the SHA-256 Base64URL hashes of the certificate's Issuer and Subject Distinguished Names (DN).
Run this PowerShell script to extract the DNs and compute the exact hashes:
function Compute-Sha256Base64Url([string]$inputString) {
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$bytes = [System.Text.Encoding]::UTF8.GetBytes($inputString)
$hashBytes = $sha256.ComputeHash($bytes)
$base64 = [Convert]::ToBase64String($hashBytes)
# Convert to Base64URL format
$base64Url = $base64.Replace('+', '-').Replace('/', '_').TrimEnd('=')
return $base64Url
}
# Load the certificate
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import("C:\Certs\DataversePluginSigning.pfx", "StrongPassword123!", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
# Extract exact DN strings as formatted by .NET
$issuerDN = $cert.Issuer
$subjectDN = $cert.Subject
$issuerHash = Compute-Sha256Base64Url -inputString $issuerDN
$subjectHash = Compute-Sha256Base64Url -inputString $subjectDN
Write-Host "Issuer DN: $issuerDN"
Write-Host "Issuer Hash: $issuerHash"
Write-Host "Subject DN: $subjectDN"
Write-Host "Subject Hash: $subjectHash"
Step 5: Configure the Federated Identity Credential (FIC) on the UAMI
Now, establish the trust relationship in the Azure Portal:
- Navigate to your User-Assigned Managed Identity in the Azure Portal.
- Under Settings, select Certificates & secrets.
- Select the Federated credentials tab, then select Add credential.
- In the Federated credential scenario dropdown, select Other issuer.
- Configure the following parameters:
- Issuer:
https://login.microsoftonline.com/{your-tenant-guid}/v2.0(Replace with your actual Tenant ID). - Audience:
api://AzureADTokenExchange(This is case-sensitive and must be exact). - Subject identifier: Construct the string using the following format:
/eid1/c/pub/t/{encodedTenantId}/a/qzXoWDkuqUa3l6zM5mM0Rw/n/plugin/e/{environmentId}/i/{issuerHash}/s/{subjectHash}Note: To get the{encodedTenantId}, parse your Tenant GUID to bytes and encode as Base64URL (see Section 5 for the script). - Name: Enter a unique name (e.g.,
fic-dataverse-plugin-prod). - Description:
Federated credential for Dataverse production plug-in assembly.
- Issuer:
- Select Add.
Step 6: Create the Managed Identity Record in Dataverse
You must register the managed identity within Dataverse by creating a record in the managedidentity table. This can be done via the Dataverse Web API or the Power Platform CLI (PAC).
Using the Web API (via an HTTP POST request):
POST https://orgname.crm.dynamics.com/api/data/v9.2/managedidentities
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
{
"applicationid": "00000000-0000-0000-0000-000000000000",
"managedidentityid": "11111111-2222-3333-4444-555555555555",
"credentialsource": 2,
"subjectscope": 1,
"tenantid": "22222222-3333-4444-5555-666666666666",
"version": 2
}
- applicationid: The Client ID of your UAMI.
- managedidentityid: A new, unique GUID for this Dataverse record.
- credentialsource: Set to
2(IsManaged). - subjectscope: Set to
1(EnvironmentScope). - tenantid: Your Microsoft Entra Tenant ID.
- version: Set to
2(to enforce Version 2 hash-based validation).
Next, bind the registered plug-in assembly to this managed identity record using a PATCH request:
PATCH https://orgname.crm.dynamics.com/api/data/v9.2/pluginassemblies(33333333-4444-5555-6666-777777777777)
Content-Type: application/json
{
"managedidentityid@odata.bind": "/managedidentities(11111111-2222-3333-4444-555555555555)"
}
Step 7: Grant the UAMI Access to Azure Key Vault
To allow the plug-in to read secrets, assign the appropriate Azure Role-Based Access Control (RBAC) role to the UAMI:
- Navigate to your Azure Key Vault in the Azure Portal.
- Select Access control (IAM) -> Add role assignment.
- Select the Key Vault Secrets User role and select Next.
- Select Managed identity under "Assign access to", then select + Select members.
- Select your subscription, filter by User-assigned managed identity, select
uami-dataverse-plugins-prod, and select Select. - Select Review + assign.
4. Complete Code Reference
The following production-grade C# plug-in demonstrates how to acquire an access token using IManagedIdentityService and retrieve a secret from Azure Key Vault. It includes robust error handling, tracing, and structured execution.
//-----------------------------------------------------------------------
// <copyright file="KeyVaultSecretRetrieverPlugin.cs" company="Enterprise Solutions">
// Copyright (c) Enterprise Solutions. All rights reserved.
// </copyright>
// <summary>
// Dataverse plug-in demonstrating credential-free authentication
// to Azure Key Vault using Power Platform Managed Identities.
// </summary>
//-----------------------------------------------------------------------
namespace Microsoft.PowerPlatform.Samples
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.Serialization.Json;
using System.Text;
using Microsoft.Xrm.Sdk;
/// <summary>
/// A Dataverse plug-in that retrieves a secret from Azure Key Vault using Managed Identity.
/// Registered on the Pre-Operation stage of the Create/Update message of target entities.
/// </summary>
public sealed class KeyVaultSecretRetrieverPlugin : IPlugin
{
/// <summary>
/// The target Azure Key Vault secret URI. In production, retrieve this from an Environment Variable.
/// </summary>
private const string KeyVaultSecretUri = "https://kv-enterprise-prod.vault.azure.net/secrets/ExternalApiKey/?api-version=7.4";
/// <summary>
/// The standard OAuth 2.0 scope required for Azure Key Vault data plane operations.
/// </summary>
private static readonly string[] KeyVaultScopes = new string[] { "https://vault.azure.net/.default" };
/// <summary>
/// Executes the plug-in logic.
/// </summary>
/// <param name="serviceProvider">The service provider containing platform services.</param>
public void Execute(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new ArgumentNullException(nameof(serviceProvider));
}
// Retrieve the execution context and tracing service
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
tracingService.Trace("KeyVaultSecretRetrieverPlugin: Execution started. Depth: {0}", context.Depth);
// Retrieve the Managed Identity Service
IManagedIdentityService managedIdentityService = (IManagedIdentityService)serviceProvider.GetService(typeof(IManagedIdentityService));
if (managedIdentityService == null)
{
tracingService.Trace("Error: IManagedIdentityService is not available. Ensure Managed Identity is configured for this assembly.");
throw new InvalidPluginExecutionException("Managed Identity Service is unavailable. Please contact your system administrator.");
}
string accessToken;
try
{
tracingService.Trace("Attempting to acquire access token for Key Vault scope.");
accessToken = managedIdentityService.AcquireToken(KeyVaultScopes);
if (string.IsNullOrEmpty(accessToken))
{
throw new InvalidPluginExecutionException("Acquired token was null or empty.");
}
tracingService.Trace("Access token successfully acquired.");
}
catch (Exception ex)
{
tracingService.Trace("Exception occurred during token acquisition: {0}", ex.ToString());
throw new InvalidPluginExecutionException("Failed to authenticate to Azure resources using Managed Identity.", ex);
}
// Retrieve the secret from Azure Key Vault using the acquired token
try
{
string secretValue = RetrieveSecretFromKeyVault(KeyVaultSecretUri, accessToken, tracingService);
tracingService.Trace("Successfully retrieved secret from Key Vault. Length: {0}", secretValue.Length);
// Perform business logic with the retrieved secret (e.g., set a field value or call an external API)
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity targetEntity)
{
targetEntity["new_decryptedapikey"] = secretValue;
}
}
catch (Exception ex)
{
tracingService.Trace("Exception occurred during Key Vault secret retrieval: {0}", ex.ToString());
throw new InvalidPluginExecutionException("Failed to retrieve configuration secrets from Azure Key Vault.", ex);
}
}
/// <summary>
/// Performs an authenticated HTTP GET request to Azure Key Vault to retrieve the secret value.
/// </summary>
private static string RetrieveSecretFromKeyVault(string secretUri, string token, ITracingService tracingService)
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
tracingService.Trace("Sending GET request to Key Vault URI: {0}", secretUri);
using (HttpResponseMessage response = httpClient.GetAsync(secretUri).GetAwaiter().GetResult())
{
string responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
if (!response.IsSuccessStatusCode)
{
tracingService.Trace("Key Vault returned error status code: {0}. Response: {1}", response.StatusCode, responseContent);
throw new HttpRequestException($"Key Vault request failed with status code {response.StatusCode}");
}
// Parse the JSON response to extract the secret value
using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent)))
{
var serializer = new DataContractJsonSerializer(typeof(KeyVaultSecretResponse));
var secretResponse = (KeyVaultSecretResponse)serializer.ReadObject(memoryStream);
return secretResponse?.Value ?? throw new InvalidDataException("Secret value was missing from Key Vault response.");
}
}
}
}
/// <summary>
/// Data contract representing the Azure Key Vault secret response payload.
/// </summary>
[System.Runtime.Serialization.DataContract]
private class KeyVaultSecretResponse
{
[System.Runtime.Serialization.DataMember(Name = "value")]
public string Value { get; set; }
[System.Runtime.Serialization.DataMember(Name = "id")]
public string Id { get; set; }
}
}
}
5. Configuration & Environment Setup
Tenant ID Base64URL Encoding Script
To construct the Federated Identity Credential subject path, you must encode your Tenant GUID into a URL-safe Base64 string. Use this PowerShell script:
$tenantId = "22222222-3333-4444-5555-666666666666"
$tenantGuid = [System.Guid]::Parse($tenantId)
$tenantBytes = $tenantGuid.ToByteArray()
$base64 = [System.Convert]::ToBase64String($tenantBytes)
$encodedTenantId = $base64.Replace('+', '-').Replace('/', '_').TrimEnd('=')
Write-Host "Encoded Tenant ID: $encodedTenantId"
Bicep Template for Infrastructure Provisioning
The following Bicep template provisions the User-Assigned Managed Identity, the Azure Key Vault, configures the Federated Identity Credential (FIC) for a Dataverse environment, and assigns the Key Vault Secrets User role.
@description('The region where resources will be deployed.')
param location string = resourceGroup().location
@description('The Client ID of the User-Assigned Managed Identity.')
param uamiName string = 'uami-dataverse-plugins-prod'
@description('The name of the Azure Key Vault.')
param keyVaultName string = 'kv-enterprise-prod'
@description('The Dataverse Environment ID (GUID).')
param dataverseEnvironmentId string
@description('The Microsoft Entra Tenant ID.')
param tenantId string
@description('The computed SHA-256 Base64URL hash of the certificate Issuer DN.')
param certificateIssuerHash string
@description('The computed SHA-256 Base64URL hash of the certificate Subject DN.')
param certificateSubjectHash string
// 1. Provision the User-Assigned Managed Identity
resource uami 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: uamiName
location: location
}
// 2. Provision the Azure Key Vault with Azure RBAC enabled
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: keyVaultName
location: location
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: tenantId
enableRbacAuthorization: true
enabledForDeployment: false
enabledForDiskEncryption: false
enabledForTemplateDeployment: false
}
}
// 3. Configure the Federated Identity Credential (FIC) Version 2
// We must compute the Base64URL encoded Tenant ID inside Bicep or pass it.
// For simplicity, we construct the subject identifier using the pre-calculated hashes.
resource federatedCredential 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = {
parent: uami
name: 'fic-dataverse-plugin-prod'
properties: {
issuer: 'https://login.microsoftonline.com/`${tenantId}`/v2.0'
audiences: [
'api://AzureADTokenExchange'
]
// Note: Ensure the encoded tenant ID is passed correctly in real scenarios.
// This subject path uses the standard public cloud prefix.
subject: '/eid1/c/pub/t/I_g-IjNDREVEV_V_V_V_V_g/n/plugin/e/`${dataverseEnvironmentId}`/i/`${certificateIssuerHash}`/s/`${certificateSubjectHash}`'
}
}
// 4. Assign Key Vault Secrets User Role to the UAMI
var keyVaultSecretsUserRoleId = '4633e12f-d780-4111-af0d-530c49090af4' // Standard Built-in Role ID
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(keyVault.id, uami.id, keyVaultSecretsUserRoleId)
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', keyVaultSecretsUserRoleId)
principalId: uami.properties.principalId
principalType: 'ServicePrincipal'
}
}
output uamiClientId string = uami.properties.clientId
output keyVaultUri string = keyVault.properties.vaultUri
6. Security & Permission Matrix
To ensure least-privilege access control, configure permissions according to the following matrix:
| Principal | Permission / Role | Scope | Reason |
|---|---|---|---|
| User-Assigned Managed Identity (UAMI) | Key Vault Secrets User | Azure Key Vault (Data Plane) | Allows the plug-in to read secret values without administrative rights. |
| Dataverse Application User | Custom Security Role (e.g., "Integration Service") | Dataverse Environment | Required if the external Azure resource needs to call back into Dataverse. |
| Deployment Service Principal (ALM) | Managed Identity Contributor | Azure Subscription / Resource Group | Required to create and update UAMIs and FICs during CI/CD pipelines. |
| Dataverse System Administrator | System Administrator | Dataverse Environment | Required to register plug-in assemblies and bind them to managed identities. |
| Power Platform Admin / Global Admin | Power Platform Administrator | Tenant / Environment | Required to link enterprise policies and manage environment-level identities. |
7. Pipeline Execution Internals
When a plug-in executes within the Dataverse event execution pipeline, the runtime manages the lifecycle of the IManagedIdentityService and token exchange.
[Client Request]
|
v
Stage 10: Pre-Validation (Sync)
|
v
Stage 20: Pre-Operation (Sync) <-- [Managed Identity Token Acquisition & Key Vault Call]
|
v
Stage 30: Main Operation (Dataverse Database Transaction Boundary)
|
v
Stage 40: Post-Operation (Sync)
|
v
[Database Commit]
|
v
Stage 40: Post-Operation (Async) <-- [Managed Identity Token Acquisition & External API Call]
Transaction Boundaries and Performance
- Synchronous Stages (Pre-Operation / Post-Operation): If the plug-in executes within a synchronous stage, the call to
IManagedIdentityService.AcquireTokenand any subsequent external HTTP calls (e.g., to Azure Key Vault) occur inside the Dataverse database transaction.- Critical Impact: Any latency or timeout in the external call directly extends the SQL transaction lock duration. If the external call takes 5 seconds, the Dataverse database transaction remains open for an additional 5 seconds, increasing the risk of SQL blocking and timeouts for concurrent users.
- Mitigation: Keep synchronous external calls extremely lightweight. Set aggressive timeouts (e.g., 2-5 seconds) on HTTP clients.
- Asynchronous Stage (Post-Operation): For non-blocking integrations (e.g., sending data to an external API), register the plug-in step as Asynchronous. This executes the plug-in outside the main database transaction, eliminating transaction lock overhead.
Sandbox Isolation and Callout Restrictions
Dataverse plug-ins execute within a highly restricted Sandbox Isolation Mode.
- Network Restrictions: Plug-ins can only communicate via standard HTTP/HTTPS protocols (ports 80 and 443). IP addresses must be publicly resolvable; calls to internal virtual networks or private endpoints are blocked unless Azure ExpressRoute or Power Platform VNet Integration is configured.
- Execution Timeout: The sandbox worker process enforces a strict 2-minute (120-second) execution timeout. If the plug-in (including token acquisition and external API calls) exceeds this limit, the platform terminates the process, rolls back the transaction, and throws a
System.TimeoutException.
8. Error Handling & Retry Patterns
Network calls and token exchanges are subject to transient failures. Plug-ins must implement structured exception handling and retry patterns to ensure resilience.
Common Failure Modes and Error Codes
| Error Code / Exception | Root Cause | Remediation |
|---|---|---|
AADSTS700213 | No matching federated identity record found. The subject identifier computed at runtime does not match the FIC configured on the UAMI. | Verify the subject path format. Ensure the certificate hashes, environment ID, and encoded Tenant ID are correct. |
AADSTS70050 | The Federated Managed Identity path is not properly formatted. Typically caused by non-ASCII characters in the certificate CN under Version 1. | Upgrade the managed identity record and FIC to Version 2 (hash-based). |
System.TimeoutException | The external call (Key Vault or token endpoint) exceeded the 120-second sandbox execution limit. | Implement aggressive timeouts on the HTTP client and offload long-running processes to asynchronous steps. |
HttpRequestException (403 Forbidden) | The UAMI authenticated successfully, but lacks permissions on the target Azure resource. | Verify Azure RBAC role assignments (e.g., ensure the UAMI has the "Key Vault Secrets User" role). |
Resilient Retry Pattern Implementation
The following code snippet demonstrates how to implement an exponential back-off retry pattern for transient network errors when calling external resources:
using System;
using System.Net.Http;
using System.Threading;
using Microsoft.Xrm.Sdk;
public static class ResilientHttpCaller
{
private const int MaxRetries = 3;
private const int InitialDelayMilliseconds = 500;
public static string CallWithRetry(Func<string> httpCall, ITracingService tracingService)
{
int retryCount = 0;
int delay = InitialDelayMilliseconds;
while (true)
{
try
{
return httpCall();
}
catch (Exception ex) when (IsTransient(ex))
{
retryCount++;
if (retryCount > MaxRetries)
{
tracingService.Trace("Max retries ({0}) reached. Failing operation.", MaxRetries);
throw;
}
tracingService.Trace("Transient error encountered: {0}. Retrying in {1}ms (Attempt {2} of {3}).",
ex.Message, delay, retryCount, MaxRetries);
Thread.Sleep(delay);
delay *= 2; // Exponential back-off
}
}
}
private static bool IsTransient(Exception ex)
{
if (ex is HttpRequestException)
{
// Retry on network-level failures
return true;
}
if (ex is TimeoutException)
{
return true;
}
return false;
}
}
9. Performance Optimisation & Limits
To maintain platform stability and performance, developers must design integrations within the following limits:
Platform Limits
- Sandbox Execution Limit: 120 seconds per plug-in execution.
- Token Cache Duration: Access tokens acquired via
IManagedIdentityServiceare cached internally by the Power Platform runtime. The cache respects the token's Time-To-Live (TTL), typically 24 hours. Do not implement custom token caching within the plug-in code; callingAcquireTokenrepeatedly is highly optimized and retrieves the cached token automatically. - API Throughput Caps: Dataverse API requests are subject to service protection limits. External calls from plug-ins do not count against Dataverse API limits, but they are subject to the throttling limits of the target Azure resource (e.g., Azure Key Vault enforces a limit of 2,000 transactions per 10 seconds for standard vaults).
Optimization Strategies
- Avoid Synchronous Key Vault Calls on High-Frequency Operations: If a plug-in runs on the
RetrieveorRetrieveMultiplemessages of a high-volume entity, do not call Key Vault synchronously. Instead, use Environment Variables with a secret store reference, which are natively cached by the Dataverse infrastructure. - Minimize Payload Sizes: When sending data to external APIs, serialize only the required attributes. Avoid serializing the entire
Entityobject, which contains metadata overhead. - Use Keep-Alive Connections: Reusing HTTP connections reduces TCP handshake latency. In Dataverse plug-ins, instantiate the
HttpClientusing a static or thread-safe pattern, or utilizeHttpMessageHandlerto manage connection lifecycles efficiently within the sandbox limits.
10. ALM & Deployment Checklist
Deploying components utilizing Managed Identities across environments (Dev -> Test -> Prod) requires careful coordination of Azure resources and Dataverse solutions.
[Development Environment]
- Register Plug-in Assembly
- Create Dev UAMI & FIC (Dev Cert)
- Bind Assembly to Dev UAMI
- Export Solution (Managed)
|
v
[Azure DevOps / GitHub Pipeline]
- Extract Solution
- Inject Target Environment Variables
- Deploy Managed Solution
|
v
[Production Environment]
- Import Managed Solution
- Create Prod UAMI & FIC (Prod Cert)
- Bind Assembly to Prod UAMI (via Deployment Script)
Step-by-Step ALM Checklist
- Source Control: Commit the plug-in source code and the strong-name key file or certificate public key (
.cer) to your Git repository. - Solution Packaging: Include the plug-in assembly and plug-in steps in your Dataverse solution. Export the solution as Managed from the development environment.
- Azure Resource Provisioning: Use Bicep or ARM templates within your CI/CD pipeline to provision the target environment's UAMI, Key Vault, and role assignments.
- FIC Configuration: Ensure the pipeline computes the correct certificate hashes for the target environment's signing certificate and configures the FIC on the target UAMI.
- Environment Variables: Use Dataverse Environment Variables to store the Key Vault secret URIs. Do not hardcode URIs in the C# code.
- Post-Deployment Binding: Since the
managedidentityrecord GUID and thepluginassemblyID may differ across environments, execute a post-deployment PowerShell script (using PAC CLI) to create themanagedidentityrecord in the target environment and bind it to the imported assembly.
Azure DevOps Pipeline YAML Snippet
The following YAML snippet demonstrates how to automate the deployment of a Dataverse solution and configure the managed identity binding using the Power Platform Build Tools:
trigger:
- main
pool:
vmImage: 'windows-latest'
variables:
SolutionName: 'EnterpriseIntegrationSolution'
TargetEnvUrl: 'https://org-prod.crm.dynamics.com'
steps:
- task: PowerPlatformToolInstaller@2
displayName: 'Install Power Platform Build Tools'
- task: PowerPlatformImportSolution@2
displayName: 'Import Managed Solution to Production'
inputs:
authenticationType: 'PowerPlatformSPN'
PowerPlatformSPN: 'ProductionServiceConnection'
SolutionInputFile: '$(Build.ArtifactStagingDirectory)/$(SolutionName)_managed.zip'
OverwriteUnmanagedCustomizations: true
PublishCustomizations: true
- task: PowerShell@2
displayName: 'Configure Managed Identity Binding'
inputs:
targetType: 'inline'
script: |
# Install PAC CLI
$env:Path += ";C:\Program Files\Microsoft Power Platform CLI\bin"
# Authenticate to target environment
pac auth create --url $(TargetEnvUrl) --tenant $(TenantId) --applicationId $(ClientId) --clientSecret $(ClientSecret)
# Retrieve the imported Plugin Assembly ID
# (In production, query the Web API or use a pre-defined mapping)
$assemblyId = "33333333-4444-5555-6666-777777777777"
$uamiClientId = "00000000-0000-0000-0000-000000000000"
$tenantId = "22222222-3333-4444-5555-666666666666"
# Create and link the managed identity record
pac managed-identity create --application-id $uamiClientId --tenant-id $tenantId --component-id $assemblyId --component-type PluginAssembly
11. Common Pitfalls & Troubleshooting Guide
1. Error AADSTS700213: No matching federated identity record found
- Symptom: The plug-in fails during execution with an
InvalidPluginExecutionExceptioncontaining the Entra ID error codeAADSTS700213. - Root Cause: The subject identifier computed by the Dataverse runtime does not match any Federated Identity Credential configured on the target UAMI.
- Diagnostic Steps:
- Check the plug-in trace log to inspect the runtime context.
- Run
pac managed-identity show-ficto view the computed subject identifier. - Compare the computed path character-by-character with the path configured in the Azure Portal.
- Fix: Update the FIC in the Azure Portal to match the exact computed subject identifier. Ensure the Tenant ID, Environment ID, and certificate hashes are correct.
2. Error AADSTS70050: Path is not properly formatted
- Symptom: Token acquisition fails with error
AADSTS70050. - Root Cause: The certificate used to sign the assembly contains non-ASCII characters in its Distinguished Name (DN), and the managed identity record is configured to use Version 1 (CN-based) validation.
- Diagnostic Steps: Inspect the certificate's Subject property. Look for accented characters or special symbols.
- Fix: Upgrade the managed identity record to Version 2 by setting the
versionattribute to2in themanagedidentitytable, and update the FIC subject path to use the hash-based format.
3. Missing Assembly Signing
- Symptom: The Plug-in Registration Tool allows registration, but token acquisition fails at runtime with a "Managed Identity is not configured" error.
- Root Cause: The assembly was registered without being cryptographically signed, or the signature was stripped during a build step.
- Diagnostic Steps: Run
signtool verify /v MyAssembly.dllto verify the signature. - Fix: Ensure the assembly is signed with a valid
.pfxcertificate during the release build pipeline.
4. Mismatched Certificate Hashes
- Symptom: The plug-in works in the development environment but fails immediately upon deployment to production.
- Root Cause: The production assembly was signed with a different certificate than the development assembly, but the production FIC was configured using the development certificate's hashes.
- Diagnostic Steps: Run the PowerShell hashing script (Section 4) against the production
.pfxfile and compare the output with the production FIC configuration. - Fix: Update the production FIC with the hashes computed from the production signing certificate.
5. Key Vault Firewall Blocking Access
- Symptom: Token acquisition succeeds, but the HTTP call to Key Vault times out or returns a
403 Forbiddenerror. - Root Cause: The Key Vault firewall is enabled, and it is blocking requests from the Power Platform sandbox IP addresses.
- Diagnostic Steps: Check the Key Vault firewall settings. Temporarily allow access from "All networks" to test.
- Fix: Configure the Key Vault firewall to allow trusted Microsoft services, or configure Power Platform VNet Integration to route sandbox traffic through a secured subnet.
6. Sandbox Execution Timeout (120 seconds)
- Symptom: The plug-in execution is terminated with a
TimeoutException. - Root Cause: The external API call or Key Vault request is taking too long, exceeding the sandbox limit.
- Diagnostic Steps: Review the plug-in trace logs to measure the duration of the token acquisition and HTTP calls.
- Fix: Implement aggressive timeouts on the
HttpClient(e.g., 5 seconds) and register the plug-in step as Asynchronous if synchronous execution is not strictly required.
7. Incorrect Audience in FIC
- Symptom: Token acquisition fails with
AADSTS700213. - Root Cause: The audience in the FIC is set to the Key Vault URI instead of the token exchange endpoint.
- Diagnostic Steps: Inspect the FIC properties in the Azure Portal.
- Fix: Ensure the audience is set to exactly
api://AzureADTokenExchange(case-sensitive).
8. Expired Signing Certificate
- Symptom: Token acquisition begins failing after months of successful operation.
- Root Cause: The certificate used to sign the plug-in assembly has expired.
- Diagnostic Steps: Check the validity period of the certificate in the local store or by inspecting the assembly properties.
- Fix: Generate a new certificate, sign the assembly, update the FIC with the new hashes, and redeploy.
9. Missing Role Assignment on Target Resource
- Symptom: Token acquisition succeeds, but the plug-in receives a
403 Unauthorizedwhen calling the Azure resource. - Root Cause: The UAMI has not been granted the necessary RBAC permissions on the target resource.
- Diagnostic Steps: Check the Access Control (IAM) blade of the target resource.
- Fix: Assign the appropriate role (e.g., "Key Vault Secrets User" or "Storage Blob Data Contributor") to the UAMI.
10. Cross-Tenant Token Acquisition Failure
- Symptom: Token acquisition fails when attempting to access resources in a different Entra ID tenant.
- Root Cause: Workload Identity Federation does not support cross-tenant token exchange by default.
- Diagnostic Steps: Verify that the UAMI and the Dataverse environment reside within the same tenant.
- Fix: Ensure all resources, UAMIs, and Dataverse environments are provisioned within the same Microsoft Entra tenant boundary.
12. Exam Focus: Key Facts & Edge Cases
To prepare for the PL-400 (Microsoft Power Platform Developer) exam, memorize the following key facts, limits, and behaviors:
- IManagedIdentityService: This is the exact interface used in C# plug-in code to acquire access tokens. It is retrieved from the
serviceProviderusingtypeof(IManagedIdentityService). - AcquireToken Signature: The method signature is
string AcquireToken(IEnumerable<string> scopes). It accepts a collection of scopes (e.g.,https://vault.azure.net/.default) and returns the token string. - FIC Audience: The audience configured on the Federated Identity Credential must be exactly
api://AzureADTokenExchange(case-sensitive). Any other value will cause authentication to fail. - FIC Issuer: The issuer URL must be
https://login.microsoftonline.com/{tenantId}/v2.0. - Version 2 Subject Path: Understand the structure of the Version 2 subject path:
/eid1/c/pub/t/{encodedTenantId}/a/qzXoWDkuqUa3l6zM5mM0Rw/n/plugin/e/{environmentId}/i/{issuerHash}/s/{subjectHash} - Strong Name Signing: Plug-in assemblies must be signed with a certificate or strong name key file to utilize Managed Identities. Unsigned assemblies cannot perform token exchange.
- Sandbox Isolation: Managed Identity token exchange is fully supported within the Dataverse sandbox isolation mode. It does not require bypassing sandbox restrictions.
- Caching: The Power Platform runtime automatically caches tokens acquired via
IManagedIdentityService. Developers should not implement custom caching logic within the plug-in code. - Supported Components: Power Platform Managed Identity is generally available (GA) for Dataverse plug-ins and dependent assembly plug-ins. It is not supported for client-side JavaScript or legacy workflow activities.
- National Clouds: When deploying to specialized cloud environments (e.g., GCC High, China), the audience and subject prefix must be explicitly modified (e.g.,
api://AzureADTokenExchangeUSGovfor GCC High).