---
source_url: https://itmustbecode.com/how-to-secure-a-dataverse-plug-in-with-managed-identity-using-plugin-identity-manager-for-xrmtoolbox/
title: How to Secure a Dataverse Plug-in with Managed Identity using Plugin Identity Manager for XrmToolBox
date: 2024-11-03T19:41:42+00:00
categories:
  - blog
  - projects-and-tools
tags:
  - azure
  - custom-api
  - managed-identity
  - plugin
  - xrmtoolbox
word_count: 2357
reading_time_minutes: 12
type: posts
---

The ability to assign **[managed identities](https://learn.microsoft.com/en-us/power-platform/admin/managed-identity-overview)** 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](https://learn.microsoft.com/en-us/power-platform/admin/set-up-managed-identity), 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](https://github.com/drivardxrm/Driv.XTB.PluginIdentityManager)** for the **[XrmToolBox](https://www.xrmtoolbox.com/)**.

{{< figure src="./image-9.png" alt="" caption="" >}}

{{< figure src="./image.png" alt="" caption="" >}}

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](https://medium.com/rapha%C3%ABl-pothin/power-platforms-protection-managed-identity-for-dataverse-plug-ins-0ae0ed405338) 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.

{{< figure src="./image-12.png" alt="" caption="" >}}

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](/customapi-manager-for-xrmtoolbox/) tool for XrmToolBox_

{{< figure src="./image-16.png" alt="" caption="" >}}

The code looks like this and basically recreate the following **[Rest Api call](https://learn.microsoft.com/en-us/rest/api/keyvault/secrets/get-secret/get-secret?view=rest-keyvault-secrets-7.4&tabs=HTTP?WT.mc_id=DX-MVP-5004959)** 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](https://learn.microsoft.com/en-us/power-platform/admin/set-up-managed-identity?WT.mc_id=DX-MVP-5004959)

## 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.

{{< figure src="./image-10.png" alt="" caption="" >}}


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**](https://coldridge.azurewebsites.net/2024/10/14/set-up-managed-identity-for-power-platform-plugins/) by [Clive Oldridge](https://www.linkedin.com/in/cliveoldridge/). 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](https://coldridge.azurewebsites.net/2024/10/14/set-up-managed-identity-for-power-platform-plugins/)

```
$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.

{{< figure src="./image-11.png" alt="" caption="" >}}

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
```

{{< figure src="./image-15.png" alt="" caption="" >}}

Once the plug-in assembly is signed, you can assess the presence of the signature by inspecting the file properties.

{{< figure src="./image-13.png" alt="" caption="" >}}

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**.

{{< figure src="./image-1.png" alt="" caption="" >}}

Assign the identity to a resource group and give it a name.

{{< figure src="./image-1-1.png" alt="" caption="" >}}

Once created, keep the **Client ID** at hand it will be needed later on.

{{< figure src="./image-4.png" alt="" caption="" >}}

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.

{{< figure src="./image-1-2.png" alt="" caption="" >}}

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**

{{< figure src="./image-5.png" alt="" caption="" >}}

- **Thumbprint of the certificate**

Navigate to the **Federated credentials** tab and click on **Add Credential**

{{< figure src="./image-4-1.png" alt="" caption="" >}}

Select **Other** as **Federated credential scenario**

{{< figure src="./image-2.png" alt="" caption="" >}}

And enter the following configurations.

{{< figure src="./image-7.png" alt="" caption="" >}}

### **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

{{< figure src="./image-7-1.png" alt="" caption="" >}}

{{< figure src="./image-8.png" alt="" caption="" >}}

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

{{< figure src="./image-11-1.png" alt="" caption="" >}}

### **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.

{{< figure src="./image-2-1.png" alt="" caption="" >}}

### **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**

{{< figure src="./image-3.png" alt="" caption="" >}}

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_.

{{< figure src="./image-5-1.png" alt="" caption="" >}}

By clicking on **Create and Link**, the managed identity record will be created and associated with the selected plug-in assembly.

{{< figure src="./image-6.png" alt="" caption="" >}}

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**

{{< figure src="./image-8-1.png" alt="" caption="" >}}

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.

{{< figure src="./image-9-1.png" alt="" caption="" >}}

### **Update/Delete a Managed Identity Record**

The tool allows modification of a Managed Identity record...

{{< figure src="./image-13-1.png" alt="" caption="" >}}

{{< figure src="./image-12-1.png" alt="" caption="" >}}

... And deletion as well

{{< figure src="./image-14.png" alt="" caption="" >}}

{{< figure src="./image-15-1.png" alt="" caption="" >}}

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**.

{{< figure src="./image-20.png" alt="" caption="" >}}

Using the **[Custom API Tester](https://github.com/rappen/CustomActionTester)** Tool from [Jonas Rapp](https://www.linkedin.com/in/rappen/), 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.

{{< figure src="./image-17.png" alt="" caption="" >}}

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**.

{{< figure src="./image-18.png" alt="" caption="" >}}

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](https://github.com/drivardxrm/Driv.XTB.PluginIdentityManager)

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,

## Links
{{< linkcard url="https://github.com/drivardxrm/Driv.XTB.PluginIdentityManager" title="GitHub - drivardxrm/Driv.XTB.PluginIdentityManager: XrmToolBox Tool to help manage Managed Identity records in Dataverse" summary="XrmToolBox Tool to help manage Managed Identity records in Dataverse - drivardxrm/Driv.XTB.PluginIdentityManager" image="/linkcards/74a6df6a75c4b42d.png" domain="github.com" new_tab="true" nofollow="true" >}}


{{< linkcard url="https://learn.microsoft.com/en-us/power-platform/admin/managed-identity-overview?WT.mc_id=DX-MVP-5004959" title="Power Platform managed identity overview (preview) - Power Platform" summary="Learn about managed identity for Power Platform and Dynamics 365 apps." image="/linkcards/4fac7452f522bab5.png" domain="learn.microsoft.com" new_tab="true" nofollow="true" >}}


{{< linkcard url="https://learn.microsoft.com/en-us/power-platform/admin/set-up-managed-identity?WT.mc_id=DX-MVP-5004959" title="Set up managed identity for Power Platform (preview) - Power Platform" summary="Learn how to set up Power Platform managed identity." image="/linkcards/4fac7452f522bab5.png" domain="learn.microsoft.com" new_tab="true" nofollow="true" >}}


{{< linkcard url="https://coldridge.azurewebsites.net/2024/10/14/set-up-managed-identity-for-power-platform-plugins/" title="Set up managed identity for Power Platform Plugins - Clive Oldridge on Power Platform Blog" summary="In the following blog, I’m going to guide you on the set up of managed identity for Power Platform. Once we have finished you should have everything you need to be able to create a plugin that can communicate with an azure resource. Why do we want to use manage identity? Managed identity allows your […]" image="/linkcards/dc1e458b24a6c15d.jpg" domain="coldridge.azurewebsites.net" new_tab="true" nofollow="true" >}}


{{< linkcard url="https://medium.com/rapha%C3%ABl-pothin/power-platforms-protection-managed-identity-for-dataverse-plug-ins-0ae0ed405338" title="Power Platform’s protection — Managed Identity for Dataverse plug-ins" summary="Once the network traffic is secured it is time to get out of the way identity credentials to integrate with Azure resources" image="/linkcards/31cfa89efc85ad0c.png" domain="medium.com" new_tab="true" nofollow="true" >}}


{{< linkcard url="https://learn.microsoft.com/en-us/power-platform/release-plan/2024wave1/power-platform-governance-administration/use-managed-identities-dataverse-plug-ins?WT.mc_id=DX-MVP-5004959" title="Use managed identities for Dataverse plug-ins" summary="Power Platform managed identities allow publishers of Dataverse plug-ins to securely connect to Azure resources without having to store or expose credentials." image="/linkcards/4fac7452f522bab5.png" domain="learn.microsoft.com" new_tab="true" nofollow="true" >}}

{{< linkcard url="https://malvankaraniket.wordpress.com/2024/08/14/power-platform-managed-identity-token-generation-w-o-application-id-and-secret/" title="Power Platform | Managed Identity – bearer token generation w/o Application Id and Secret" summary="Reference – [Note : This is in Preview] Power Platform managed identity allows Dataverse plug-ins to connect with Azure resources supporting managed identity without the need of credentials. …" image="https://malvankaraniket.wordpress.com/wp-content/uploads/2024/08/image-9.png" domain="malvankaraniket.wordpress.com" new_tab="true" nofollow="true" >}}

[Image by freepik](https://www.freepik.com/free-vector/neon-fingerprint-background_6148287.htm#from_view=detail_alsolike)


