Sets up and manages Dataverse Managed Identities for plugin assemblies: creates UAMIs, computes federated credentials, registers records in Dataverse, associates assemblies, configures CI/CD signing. Useful for secure Azure resource access without credentials.
How this skill is triggered — by the user, by Claude, or both
Slash command
/dynamics365-dataverse:managed-identityThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
The user wants to set up or manage Dataverse Managed Identities for plugin assemblies to securely access Azure resources (Key Vault, Storage, APIs, etc.) without storing credentials.
The user wants to set up or manage Dataverse Managed Identities for plugin assemblies to securely access Azure resources (Key Vault, Storage, APIs, etc.) without storing credentials.
Argument provided: $ARGUMENTS
Managed Identity allows Dataverse plugins to acquire Azure AD tokens for Azure resources (Key Vault, Azure SQL, Storage, custom APIs) without embedding secrets. It uses User-Assigned Managed Identities (UAMI) with Federated Identity Credentials.
Architecture:
Plugin Code → IManagedIdentityService.AcquireToken() → Azure AD → Federated Credential → UAMI → Azure Resource
Collect from the user:
To get the Dataverse environment ID, use:
get_org_settings → the organizationid value
Or the user can find it in Power Platform Admin Center → Environments → Environment Details.
One UAMI per environment (or shared if acceptable). Guide the user through Azure CLI or Portal:
# Create UAMI for each environment
az identity create --name "mi-dataverse-dev" --resource-group "rg-dataverse" --location "westeurope"
az identity create --name "mi-dataverse-int" --resource-group "rg-dataverse" --location "westeurope"
az identity create --name "mi-dataverse-test" --resource-group "rg-dataverse" --location "westeurope"
az identity create --name "mi-dataverse-uat" --resource-group "rg-dataverse" --location "westeurope"
az identity create --name "mi-dataverse-prod" --resource-group "rg-dataverse" --location "westeurope"
Save each UAMI's Client ID (Application ID) from the output.
⚠️ CRITICAL: The plugin assembly must be signed with a certificate. The certificate's SHA-256 hash is used in the federated credential subject.
# PowerShell — Generate self-signed certificate
$cert = New-SelfSignedCertificate `
-Subject "CN=DataversePluginSigning" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyExportPolicy Exportable `
-KeySpec Signature `
-KeyLength 2048 `
-KeyAlgorithm RSA `
-HashAlgorithm SHA256 `
-NotAfter (Get-Date).AddYears(5)
# Export as PFX (with private key)
$password = ConvertTo-SecureString -String "YourPassword" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath ".\plugin-signing.pfx" -Password $password
# Get the SHA-256 hash (hex format — CRITICAL: must be hex, not base64url)
$hash = $cert.GetCertHash("SHA256")
$hexHash = ($hash | ForEach-Object { $_.ToString("x2") }) -join ""
Write-Host "Certificate SHA-256 Hex Hash: $hexHash"
Use the compute_federated_credential_subject MCP tool for each environment. The subject format is:
/eid1/c/pub/t/{encodedTenantId}/a/{encodedAppId}/n/plugin/e/{environmentId}/h/{certHashHex}
Key encoding rules:
+→-, /→_)The MCP tool handles all encoding automatically.
For each environment:
az identity federated-credential create \
--identity-name "mi-dataverse-dev" \
--resource-group "rg-dataverse" \
--name "dataverse-dev-credential" \
--issuer "https://login.microsoftonline.com/{tenantId}/v2.0" \
--subject "/eid1/c/pub/t/{encodedTenantId}/a/{encodedAppId}/n/plugin/e/{envId}/h/{certHashHex}" \
--audiences "api://AzureADTokenExchange"
Repeat for each environment — only the environmentId changes in the subject.
Example for Key Vault:
# Grant each UAMI access to read secrets
az keyvault set-policy --name "kv-myapp" \
--object-id "$(az identity show -n mi-dataverse-dev -g rg-dataverse --query principalId -o tsv)" \
--secret-permissions get list
Or for RBAC-based Key Vault:
az role assignment create \
--assignee-object-id "$(az identity show -n mi-dataverse-dev -g rg-dataverse --query principalId -o tsv)" \
--role "Key Vault Secrets User" \
--scope "/subscriptions/{subId}/resourceGroups/rg-dataverse/providers/Microsoft.KeyVault/vaults/kv-myapp"
Use the create_managed_identity MCP tool for each environment:
Select environment → DEV
create_managed_identity:
application_id: "{UAMI_CLIENT_ID_FOR_DEV}"
managed_identity_id: "{CONSISTENT_GUID}" ← use same GUID across all envs!
tenant_id: "{TENANT_ID}"
⚠️ IMPORTANT: Use the same managedidentityid GUID across all environments. This allows the solution to be promoted without changing the ID.
The API call:
POST /api/data/v9.2/managedidentities
{
"applicationid": "{UAMI_CLIENT_ID}",
"managedidentityid": "{CONSISTENT_GUID}",
"credentialsource": 2,
"subjectscope": 1,
"tenantid": "{TENANT_ID}",
"version": 1
}
Use the associate_assembly_managed_identity MCP tool:
list_plugin_assemblies → find your assembly ID
associate_assembly_managed_identity:
assembly_id: "{PLUGIN_ASSEMBLY_ID}"
managed_identity_id: "{MANAGED_IDENTITY_ID}"
The API call:
PATCH /api/data/v9.2/pluginassemblies({assemblyId})
{
"[email protected]": "/managedidentities({managedIdentityId})"
}
# Sign the DLL
signtool sign /f plugin-signing.pfx /p "YourPassword" /fd SHA256 MyPlugin.dll
# Verify
signtool verify /pa MyPlugin.dll
Deploy via Plugin Registration Tool, pac plugin push, or solution import.
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
// Get Managed Identity Service
var miService = (IManagedIdentityService)serviceProvider.GetService(typeof(IManagedIdentityService));
// Acquire token for Key Vault (MUST use .default suffix)
string token = miService.AcquireToken(new[] { "https://vault.azure.net/.default" });
tracingService.Trace("Token acquired: {0}...", token.Substring(0, 20));
// Use token
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = client.GetAsync("https://kv-myapp.vault.azure.net/secrets/my-secret?api-version=7.4").Result;
tracingService.Trace("Key Vault response: {0}", response.StatusCode);
}
}
⚠️ AcquireToken gotchas:
/.default suffix: "https://vault.azure.net/.default", NOT "https://vault.azure.net"IEnumerable<string>, so pass: new[] { scope } (not a plain string)- name: Sign Plugin Assembly
run: |
# Decode PFX from secret
echo "${{ secrets.PLUGIN_CERT_PFX_BASE64 }}" | base64 -d > cert.pfx
# Sign both DLLs
signtool sign /f cert.pfx /p "${{ secrets.CERT_PASSWORD }}" /fd SHA256 MyPlugin/bin/Release/net462/MyPlugin.dll
# Clean up
rm cert.pfx
- name: Post-Import — Associate Assembly with Managed Identity
run: |
# Dynamic lookup — find the assembly GUID in the target environment
ASSEMBLY_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$ENV_URL/api/data/v9.2/pluginassemblies?\$filter=name eq 'MyPlugin'&\$select=pluginassemblyid" \
| jq -r '.value[0].pluginassemblyid')
# Associate with managed identity
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"$ENV_URL/api/data/v9.2/pluginassemblies($ASSEMBLY_ID)" \
-d '{"[email protected]": "/managedidentities(YOUR_MI_GUID)"}'
GitHub master → CI/CD (sign DLL) → INT → Dataverse pipeline → Test → UAT → Prod
signtool sign /f cert.pfx /p "password" /fd SHA256 MyPlugin.dllassociate_assembly_managed_identity to link assembly → managed identity/.defaultapi://AzureADTokenExchangenpx claudepluginhub nickmeron/dataverse-mcp-serverCreates bite-sized, testable implementation plans from specs or requirements, with file structure and task decomposition. Activates before coding multi-step tasks.