The ability to assign managed identities to Dataverse plug-ins was recently introduced in the Power Platform and was received with waves of likes and thumbs up from the Power Platform community.
Rightfully so, this powerful feature enables Dataverse plug-ins to securely connect with Azure resources without the hassle of managing credentials.
However, due to technical constraints and limited documentation, setting up plug-in managed identities is not that straightforward and can prove to be a daunting process, even for seasoned Power Platform developers.
One aspect that I found unintuitive is the creation of managed identity records in Dataverse and their association to plug-in assemblies. This sparked the idea for my latest community tool the Plugin Identity Manager for the XrmToolBox.
You can use the tool to:
š§ Create, Update and Delete Managed identity records in Dataverse
š Link Managed Identity to plugin assemblies
š Inspect existing Plugin/Identity configuration
In the following, I will show how to setup a managed identity for a Dataverse plug-in from scratch and use the Plugin Identity Manager tool for the final configuration step.
Important! at the time of writing, managed identities for Dataverse plug-ins is still in preview. Some features and implementation details might change in the future.
Managed Identities for Dataverse Plugins
In a nutshell, Azure Managed Identity enables secure, password-free access to Azure resources, simplifying both security and management in the application lifecycle. Any Azure resource that supports Azure Entra authentication can be accessed by a managed identity, opening up a wide range of scenarios.
I highly recommend these resources that goes deeper on the matter
šPower Platformās protection ā Managed Identity for Dataverse plug-ins by MVP RaphaĆ«l Pothin
šDeveloper introduction and guidelines - Managed identities for Azure resources | Microsoft Learn
As a proof of concept, I will demonstrate how to access a secret stored in an Azure Key Vault from within a Dataverse plug-in using a managed identity, removing the burden of credential management to the Azure resource.
I’ll create a Custom APIāmainly for easier testingāthat takes the name of a Key Vault and the name of a secret in the Key Vault as input and return its value as output. Iāll also output the token for testing purposes. Screenshot from my Custom API Manager tool for XrmToolBox
The code looks like this and basically recreate the following Rest Api call to the key vault.
GET {vaultBaseUrl}/secrets/{secret-name}/{secret-version}?api-version=7.4
using Microsoft.Xrm.Sdk;
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Net.Http;
namespace Dataverse.ManagedIdentity.Plugin
{
public class GetSecretValue : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Get Services.
var pluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var identityService = (IManagedIdentityService)serviceProvider.GetService(typeof(IManagedIdentityService));
var inputparameters = pluginExecutionContext.InputParameters;
var outputparameters = pluginExecutionContext.OutputParameters;
//INPUT of the Custom API
var keyvaultname = (string)inputparameters["KeyVaultName"];
var secretname = (string)inputparameters["SecretName"];
// Get Token
var scopes = new List<string> { "https://vault.azure.net/.default" };
var token = identityService.AcquireToken(scopes);
outputparameters["Token"] = token;
outputparameters["Message"] = string.Empty;
var keyvaultsecretUrl = $"https://{keyvaultname}.vault.azure.net/secrets/{secretname}?api-version=7.4";
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(keyvaultsecretUrl));
var response = client.SendAsync(request).Result;
string json = response.Content.ReadAsStringAsync().Result;
var keyVaultResponse = System.Text.Json.JsonSerializer.Deserialize<KeyVaultResponse>(json);
outputparameters["SecretValue"] = keyVaultResponse.value;
outputparameters["Success"] = true;
}
}
catch (Exception ex)
{
outputparameters["Success"] = false;
outputparameters["Message"] = ex.Message;
}
}
}
}
public class KeyVaultResponse
{
public string value { get; set; }
public string id { get; set; }
}
All the magic lies in the AcquireToken method call of the IManagedIdentityService. Without proper configuration of the managed identity on both the Azure and Dataverse sides, accessing key vault secrets will be out of reach to the plug-in code.
I will follow the official documentation and try to fill the gaps where needed.
šSet up managed identity for Power Platform (preview) - Power Platform | Microsoft Learn
Step #1 : Sign the plugin with a certificate
One of the first step in the process is to sign the plugin assembly with a certificate (.pfx signing) . This is essential otherwise an error will be thrown while trying to associate a Dataverse managed identity record to the plugin assembly.
Despite years of development on the platform, I never signed plug-in assemblies with a certificate (.pfx) and had always relied on the simpler strong name signing (.snk). As a result, this part proved to be a bit tedious for me as I am not a security expert.
The official doc is a bit scarce on this aspect but I followed the recipe described in this great post by Clive Oldridge. It shows how to create a self-signed certificate (not recommended for production) that will be enough for our experimentations.
I’m copying the powershell script here but go see the entire blog post for more context
šSet up managed identity for Power Platform Plugins – Clive Oldridge on Power Platform Blog
$ku_codeSigning = "1.3.6.1.5.5.7.3.3";
$codeSignCert = New-SelfSignedCertificate `
-Type "CodeSigningCert" `
-KeyExportPolicy "Exportable" `
-Subject "ManagedIdentityPlugin" `
-KeyUsageProperty @("Sign") `
-KeyUsage @("DigitalSignature") `
-TextExtension @("2.5.29.37={text}$($ku_codeSigning)", "2.5.29.19={text}false") `
-CertStoreLocation cert:\CurrentUser\My `
-KeyLength 2048 `
-NotAfter ([DateTime]::Now.AddDays(90)) `
-Provider "Microsoft Software Key Storage Provider";
Once created, you can inspect the certificate with certmgr. Grab the value of the thumbprint, it will be needed later.
Now, with the certificate at hand the plug-in assembly can be signed using this command.
signtool sign /n {{CERTIFICATENAME}} /fd SHA256 {{ASSEMBLYNAME}}.dll
Although I much prefer setting up a post-build command in Visual Studio to automatically sign the assembly upon every successful build of the plug-in project.
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe" sign /n {{CERTIFICATENAME}} /fd SHA256 $(SolutionDir)bin\$(Configuration)\{{ASSEMBLYNAME}}.dll
Once the plug-in assembly is signed, you can assess the presence of the signature by inspecting the file properties.
Thatās one big step down! Now, letās head over to the Azure setup.
Step #2 : Configure the managed identity in Azure
Two types of managed identities can be configured: a user-assigned managed identity or an application registered in Microsoft Entra ID.
For the current scenario, I will create a user-assigned managed identity, easily available to create from the Azure Portal.
Assign the identity to a resource group and give it a name.
Once created, keep the Client ID at hand it will be needed later on.
Next, grant the newly created identity access to the required Azure resources via the Azure role assignment tab. Here the Key Vault Secrets User RBAC role is given on the Key Vault we want to expose to the plug-in.
Now on to the trickiestāand, in my opinion, under-documentedāpart: configuring federated credentials for managed identity.
Before starting the configuration have these info at hand
- Dataverse Environment Id
- Thumbprint of the certificate
Navigate to the Federated credentials tab and click on Add Credential
Select Other as Federated credential scenario
And enter the following configurations.
Issuer Url :
Take the Dataverse Environment ID (GUID), remove the dashes, and format it by placing a period between the first 30 characters and the last 2 characters to construct the Issuer URL.
https://{ENVID_FIRST30}.{ENVID_LAST2}.environment.api.powerplatform.com/sts
ex. https://3608895ef8844a53a065b306837e60.96.environment.api.powerplatform.com/sts
2- Subject :
Important! I went through some trial and error here, but this configuration finally worked for me. The thumbprint must be in uppercase, and the environment Id should be in the usual GUID format (lowercase with dashes)
component:pluginassembly,thumbprint:{THUMBPRINT_UPPERCASE},environment:{ENVID}
ex. component:pluginassembly,thumbprint:6C79AFBC726410ECB5A821F7C4663EC0299CD748,environment:3608895e-f884-4a53-a065-b306837e6096
Audience :
By default the audience is set to api://AzureADTokenExchange. I found out that the audience needed to be set in lowercase api://azureadtokenexchange otherwise errors where thrown
Here is the error I got when using the default value, after I changed the audience to lowercase, it went through correctly.
An unexpected error occurred: Microsoft.Identity.Client.MsalServiceException: A configuration issue is preventing authentication – check the error message from the server for details. You can modify the configuration in the application registration portal. See https://aka.ms/msal-net-invalid-client for details. Original exception: AADSTS7002122: No matching federated identity record found for presented assertion audience ‘api://azureadtokenexchange‘. The audience matches with case-insensitive comparison, but not with case-sensitive comparison. Please check your federated identity credential Subject, Audience and Issuer against the presented assertion. https://learn.microsoft.com/entra/workload-id/workload-identity-federation
And now, the final piece of the puzzleāwhere I finally get to show off my tool! š
Step #3 : Configure the managed identity in Dataverse
Now that all is set in Azure, its time to register the managed identity in Dataverse and associate the record to the plugin assembly.
Since there is no available UI for Managed Identity records in Dataverse, the official documentation instruct the user to make 2 platform WebApi requests.
POST https://<<orgURL>>/api/data/v9.0/managedidentities
{
"applicationid":"<<appId>>",
"managedidentityid":"<<anyGuid>>",
"credentialsource":2,
"subjectscope":1,
"tenantid":"<<tenantId>>"
}
PATCH https:// <<orgURL>>/api/data/v9.0/pluginassemblies(<<PluginAssemblyId>>)
{
"managedidentityid@odata.bind": "/managedidentities(<<ManagedIdentityGuid>>)"
}
While, there’s nothing wrong with making Web API calls if you’re comfortable with it, I think it falls short in terms of intuitive user experience. This is where the Plugin Identity Manager for the XrmToolBox comes in and brings value to the table.
You can download the tool from the Tool Library of the XrmToolBox
Select plugin assembly
The first thing to do after firing up the tool is to choose your plug-in assembly. You have the choice to list all the plugins assembly installed in the environment of choose a given solution. You can also filter out managed or unmanaged assemblies.
Create and Assign a new Managed Identity Record
Once the plug-in selected, you can create a new Managed Identity record to associate with the assembly by clicking Link to New Identity
In the creation screen you are invited to set these parameters :
- Name : I have defaulted to ‘{Plugin Name} Identity‘ but you can enter what you want.
The official docs doesn’t highlight the Name field in the Web API call example, but itās good practice to assign a name to the record. This makes it easier to manage and include in a solution later on.
- ApplicationId : This is the ClientId of of the managed identity created in Azure.
- TenantId : the GUID of the tenant where the Azure resource is located
- Credential Source : I am defaulting and forcing the ‘IsManaged (2)‘ value as the other available values seems to be reserved by Microsoft for internal scenarios. Drop me a line in the Github repo if you want me to open this.
- Subject Scope : I default to ‘Environment Scope (1)‘ but there are 2 other values. Global Scope or DevOnly Scope.
Environment Scope limits the managed identity to the same tenant as the Dataverse environment. Global scope, on the other hand, would enable access to Azure resources located on another tenant. Note that I haven’t tested Global scope yet and am unsure if it’s available in the preview release of the feature.
By clicking on Create and Link, the managed identity record will be created and associated with the selected plug-in assembly.
At this point all the configuration is done and we can proceed to the testing phase. But here are some additional features of the tool.
Assign to an existing Managed Identity Record
If the desired Managed Identity record already exists, you also have the choice to Link to existing Identity
This will display a list of the Managed Identity records where credential source is ‘IsManaged (2)‘ allowing you to link them to the selected plug-in.
Update/Delete a Managed Identity Record
The tool allows modification of a Managed Identity record…
… And deletion as well
Without further ado, let’s proceed to the testing phase.
Step #4 : Test the plug-in
Now comes the moment of truthātesting whether the plugin code can successfully access the Azure Key Vault secret.
In the Key Vault (kv-isv-dev) that Iāve granted access to the managed identity, I have created a secret named MySecret.
Using the Custom API Tester Tool from Jonas Rapp, I can execute the plug-in code.
With the GetSecretValue Custom API (described earlier) selected, simply provide the name of the Azure Key Vault, the name of the secret and Execute the API.
If all is well configured, the value of the secret is correctly retrieved by the plug-in code. All without any credentials, thanks to the managed identity and the federated credential.
That is all for now š
Take Away
Overall, managed identities for Dataverse plug-ins is a great new feature that deserves to be on Power Platform developers radar. But, you’ll have to admit that there is a certain complexity associated with the process. Itās important to weigh the pros and cons before pursuing this approach.
I hope that some of you will find the Plugin Identity Manager tool useful to make the setup experience more enjoyable and coherent. Please submit any comments or ideas to improve the tool in the github repo
Thereās much more to discuss on the subject, such as current limitations and ALM considerations, but thatās beyond the scope of this blog post. I will certainly continue my experimentations and share my findings.
Until then,
Be First to Comment