# Integrating Microsoft Agent Framework with Microsoft Purview Services for Data Governance for AI Agents

As more organizations adopt AI-driven setups and applications the need for governance and operational security across these services has been more than important than ever before. You definitely wouldn't want your AI agents to have open access across your systems without any form of governance or compliances regulating them.

This is where **Microsoft Purview** provides a powerful platform for seamlessly securing and governing your data. Microsoft Purview helps reduce complexity and improve governance related to mitigating risks in the era of AI.

**Microsoft Agent Framework** supports seamless integration with Microsoft Purview to help implement governance and compliance for your AI agents.

If you aren't much aware of Microsoft Purview and its capabilities, I would highly recommend to get familiar with it . A good starting point can be Microsoft official documentation on Microsoft Purview [here](https://www.microsoft.com/en-in/security/business/microsoft-purview).

### Prerequisites

*   You will require Azure subscription with a **M365 E5** license. This is a must. Any other license for example E3 or Business premium or Standard license unfortunately will not work.
    
*   Also required is **Microsoft 365 pay-as-you-go** option enabled.
    
*   A thorough understanding of Microsoft Entra and OIDC authentication through **MSAL.NET** and how they integrate in the overall Microsoft ecosystem. I have extensively used them across all of my blogs on Microsoft fabric and Microsoft Azure. I would like to highlight a couple of them to make understand on how to integrate them. You can find them [here](https://www.azureguru.net/msal-for-microsoft-fabric) and [here](https://www.azureguru.net/service-principal-for-azure) .Though the articles are specific for with Microsoft fabric and Azure , they will help you to get a general understanding of the concepts.
    
*   Some conceptual understanding of Microsoft Purview like policy framework and how these policies are used to govern organizational data.
    

Below is a conceptual flow of **MSAL.NET** which is generic OAuth 2.0 and OIDC client.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/721485e8-c293-4b65-bfe2-8b2bc99f904c.png align="center")

### **SetUp**

The very first step is to setup a Service Principal in Microsoft Entra that has the following Microsoft Graph permissions :

*   **ProtectionScopes.Compute.All**
    
*   **ContentActivity.Write**
    
*   **Content.Process.All**
    
*   **Purview.DelegatedAccess**
    
*   **ProtectionScopes.Compute.All**
    

I created a one named **Purview\_Monitoring**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/aae103ba-c722-4872-ae89-405f41a9e470.png align="left")

Next , the following prerequisites has to be configured in Purview.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/e3fc7583-3e5e-495a-9716-30d772721c8d.png align="left")

> The "Configuration" names are little different in the new Purview UI

**Microsoft Purview Audit >>**

Its possible to turn it ON in two ways

Through the Purview portal or PowerShell

***Purview Portal >>***

Click **"Start recording user and admin activity"** to enable the Audit option.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/31a9237f-11c7-4555-b442-6bf288fd2a45.png align="center")

***PowerShell >>***

At the very first , to use **PowerShell** for Purview you will have to use the following command

```yaml
Connect-IPPSSession
```

to connect to Security & Compliance

> Use PowerShell command line and not PowerShell ISE

**Connect-IPPSSession** will prompt you to authenticate your credentials

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/6e24ce17-caf3-47be-8ba4-e040fd8bf409.png align="left")

Once authenticated, load to the **ExchangeOnlineManagement** cmdlet. If not already installed, the cmdlet will auto install it for you.

```yaml
Import-Module ExchangeOnlineManagement
```

Next, connect to Exchange Online PowerShell through your credentials. In my case it is **sachin.nandanwar@azureguru.net**

```yaml
Connect-ExchangeOnline -UserPrincipalName sachin.nandanwar@azureguru.net
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/d700d157-f720-40e6-bd6d-02f3a83bba61.png align="center")

and then run the following command to enable Audit logs option.

```yaml
Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true
```

It might take about 60 minutes for the Audit logs to be enabled

To check the status, under Solutions click the **DSPM for AI (Classic)** option

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/794953cc-d439-4d25-8017-b62d78194474.png align="left")

> The above option is not available under new DSPM page

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/cc358fb2-eaea-4137-8171-7f3b5f1af1fa.png align="left")

**DSPM for AI >>**

In the next step , enable the **KYD Policy.**

There is no option to set this up through the UI. You will have to set it up through **PowerShell.**

```dockerfile
$locations = "[{`"Workload`":`"Applications`",`"Location`":`"$myEntraAppId`",`"LocationDisplayName`":`"$myEntraAppName`",`"LocationSource`":`"Entra`",`"LocationType`":`"Individual`",`"Inclusions`":[{`"Type`":`"Tenant`",`"Identity`":`"All`"}]}]"

New-FeatureConfiguration `
    -FeatureScenario KnowYourData `
    -Name "Secure interactions from enterprise apps (preview)" `
    -Mode Enable `
    -ScenarioConfig '{"Activities":["UploadText","DownloadText"],"EnforcementPlanes":["Application"],"SensitiveTypeIds":["All"],"IsIngestionEnabled":true}' `
    -Locations $locations
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/615273ee-b8d1-4a40-ade2-45380687d04a.png align="center")

> I have blurred the the $myEntraAppId value. This is the ClientID of the service principal that was set earlier. Note that"Secure interactions from enterprise apps (preview)" is a policy.

Through the above PowerShell script, we are tying up the service principal with the policy.

Once executed ,under **DSPM for AI >>Policies** option you should be able to see the created policy.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/adfeb261-f5a2-4d1d-8367-0d621e204500.png align="center")

**Communication Compliance >>**

Under Solutions, click **Communication Compliance**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/0b62a332-8cdb-46ad-bb9d-131304a4caaf.png align="left")

Select the **"Detect unethical interactions for agents"** option

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/8f5d785b-7358-4cd5-9ba5-b32f7eac9e21.png align="left")

> There is an issue while setting up this policy. Its not possible to set it up for a specific Service Principal.

Check the screenshot below , where I tried to search for the service principal **Purview\_Monitoring** that was configured earlier.

Its un available in the **Select users** option.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/9dccd13e-2ac1-4428-88a6-fe63885db301.png align="left")

while the Entra users are visible

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/12a97ce4-7f5d-403d-a351-f73042b38f51.png align="left")

Anyways, setting it up for **All users** doesn't impact the flow.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/0e60ad73-2fa9-4e0a-bb2a-28bd4bf95123.png align="center")

The status is **"Activating"**. It will take sometime for the status to change to **"Ready"**.

**Insider Risk Management >>**

Under **Insider Risk Management** option select **Quick Policy**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/78ba0867-d547-451e-b7a1-04d66e7341e8.png align="left")

and select **"Risky AI Usage"** policy

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/2de834ff-1a81-4fc7-9d8d-c5fec8e6a5b3.png align="left")

This is a standard general policy and is not tied to a specific user/s .

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/f9b9ccc8-7450-494f-867b-8e9f607a6a59.png align="left")

This is the last step and this basically completes all the underlying prerequisites.

### Use Case

Now that all the prerequisites are in place, let's look through a use case in which we create a Microsoft Purview policy to block a specific prompt.

Prompts such as ***"Remember the credit card number..."*** are typically blocked by the AI model itself and there is no operational need for Microsoft Purview to validate and block the prompt. So the challenge is to create a policy that blocks a specific prompt **only** when it originates from our set up i.e. through an agent that runs the service principal.

As we saw earlier, the Microsoft Purview UI does not currently provide a way to scope policies to an individual Microsoft Entra service principal under which an Agent executes. This is where **PowerShell** comes to the rescue.

Lets create a policy that blocks a text say **"oranges"**.

To get started, Run the following commands through PowerShell command line and **NOT** PowerShell ISE.

Also ensure that you are already connected through **Connect-IPPSSession**

```dockerfile
Connect-IPPSSession

Import-Module ExchangeOnlineManagement

$myEntraAppId = "Service Principal ClientId"

$myEntraAppName = "Service Principal Name"

$locations = "[{`"Workload`":`"Applications`",`"Location`":`"$myEntraAppId`",`"LocationDisplayName`":`"$myEntraAppName`",`"LocationSource`":`"Entra`",`"LocationType`":`"Individual`",`"Inclusions`":[{`"Type`":`"Tenant`",`"Identity`":`"All`"}]}]"

New-DlpCompliancePolicy -Name "Test Oranges DLP" -Mode Enable -Locations $locations -EnforcementPlanes @("Application")
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/36f27c38-564c-40bb-96db-42d66677c811.png align="center")

It takes some time for policy status to change from **"Sync In Progress"** to **"Ready"**.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/331fe8d9-77d7-49b8-8a98-b655aa006c77.png align="center")

In the next step create a **"Sensitive info types"**.

There are a lot of predefined **"Sensitive info types"** but we will create a custom one for our use case.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/38fe7dac-0cc1-45d2-b95c-e730da976ad3.png align="center")

Name the sensitivity info type as **"My Oranges Test SIT"**.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/ae0899dd-6803-496f-afc2-b11b4e76574f.png align="center")

Next, create a pattern

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/28d1fa30-e4c5-4bcf-9c6d-35958bd91650.png align="center")

and select **"Add primary element"** and under it select **"Regular expression"** option.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/c7fc0591-a899-4dc7-a416-b5de3672f379.png align="left")

Add ID and keyword **"oranges".** The text **"oranges"** will be checked

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/d475f731-3744-467c-b25c-9aeabc2be371.png align="left")

If required, there is an option to use inbuilt regular expression by clicking the **"Choose from existing regular expressions"** in the screen above.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/d18dd9e0-c6aa-4aff-a842-63ed9ee2cfbd.png align="left")

In the next screen, keep the Confidence level to **High** for highest accuracy.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/1bac1063-1d00-49c5-a501-c67522e62ae3.png align="left")

Click **Next**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/e45f47d5-a95b-4414-95db-d9495808c477.png align="left")

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/2d109f80-01ba-4f1c-b495-b28b35931215.png align="left")

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/5c07cc30-7e52-42d1-a907-55523e168754.png align="center")

You can double check the sensitivity type through the following **PowerShell** command.

```dockerfile
Get-DlpSensitiveInformationType 'My Oranges Test SIT'
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/9a747ffc-bd1e-485c-ac1f-f44cd72a0548.png align="center")

In the next step, we will create a rule that is tied with the policy and leverages the sensitivity type that we created.

```dockerfile
New-DlpComplianceRule -Name "Test Oranges Rule" -Policy "Test Oranges DLP" -ContentContainsSensitiveInformation @{Name = "My Oranges Test SIT"}  -GenerateAlert $true -GenerateIncidentReport @("siteadmin") -NotifyUser @("user@email.com") -RestrictAccess @(@{setting="UploadText";value="Block"})
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/d16397a0-b08d-4a62-ac45-5c143cbb9dff.png align="center")

That's all the settings required in Purview.

At this point, all the prerequisites, policies and sensitivity types in Purview are configured.

### Code

In a console application install the following packages

```csharp
dotnet add package Azure;
dotnet add package  Azure.AI.OpenAI;
dotnet add package  Microsoft.Agents.AI;
dotnet add package  Microsoft.Agents.AI.Purview;
dotnet add package  Microsoft.Extensions.AI;
dotnet add package  Microsoft.Extensions.Configuration;
dotnet add package  Microsoft.Extensions.DependencyInjection;
```

After the above artifacts are in place, add the following code to read the settings from `appsettings.json` in **Program.cs** of the project.

```csharp
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.Build();
```

Read the credentials and register a Chatclient

```csharp
var credential = new AzureKeyCredential(configuration["AppSettings:ApiKey"]);

ServiceCollection servicecollection = new();

builder.Services.AddKeyedChatClient(
    "ChatClient",
    (
        sp =>
            new AzureOpenAIClient(new Uri(configuration["AppSettings:EndPoint"]), credential)
                .GetChatClient(configuration["AppSettings:Chat_DeploymentName"])
                .AsIChatClient()
    )
);
```

In the next step, register the following AIAgent in the hosted DI container.

**PurviewAgent >>**

```csharp
  servicecollection.AddSingleton<ChatClientAgent>(sp =>

  {
      Func<ChatClientAgentOptions> func = () =>
     {
         return new ChatClientAgentOptions
         {
             ChatOptions = new ChatOptions
             {
                 Instructions ="You are a secure assistant.",

             },
             Name = "PurviewAgent",
             Id = "1"
         };
     };

      return new ChatClientAgent(sp.GetKeyedService<IChatClient>("ChatClient"), options: func());

  });
```

Build service and fetch agent through **ServiceProvider** as **ChatClientAgent.**

```csharp
ServiceProvider serviceProvider = servicecollection.BuildServiceProvider();

var agent = serviceProvider.GetServices<ChatClientAgent>();

List<ChatClientAgents> chatclientagent = new(agent);
```

This is the most crucial part of the code. Here is where we leverage the Purview library

```csharp
AIAgent agent_ = chatClientAgents[0].AsBuilder().WithPurview(await .esult(), new PurviewSettings("Purview_Monitoring")
 {
     BlockedPromptMessage = "<<< BLOCKED BY PURVIEW PROMPT >>>",
     BlockedResponseMessage = "<<< BLOCKED BY PURVIEW RESPONSE >>>"
 }).Build();
```

We will have to pass **TokenCredentials** for the service principal authentication

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/698cd83c-426e-46b6-b3f0-18e806e95332.png align="left")

To implement it, I have used the authentication method where the bearer token generated by **MSAL** is converted to **TokenCredentials**.

I have a blog on this topic where I used it for Fabric **OneLake** authentication for **DataLakeServiceClient** .

[https://www.azureguru.net/customize-clientsecretcredential-class-for-onelake-authentication-in-microsoft-fabric](https://www.azureguru.net/customize-clientsecretcredential-class-for-onelake-authentication-in-microsoft-fabric)

First we will have to create a Credentials class.

**AccessTokenCredential.cs >>**

```csharp
using Azure.Core;
using Azure.Identity;
using System.IdentityModel.Tokens.Jwt;

public class AccessTokenCredential : ClientSecretCredential
    {

        public AccessTokenCredential(string accessToken)
        {
            AccessToken = accessToken;
        }

        private string AccessToken;

        public AccessToken FetchAccessToken()
        {
            JwtSecurityToken token = new JwtSecurityToken(AccessToken);
            return new AccessToken(AccessToken, token.ValidTo);
        }

        public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
        {
            return new ValueTask<AccessToken>(FetchAccessToken());
        }

         public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
         {
             JwtSecurityToken token = new JwtSecurityToken(AccessToken);
             return new AccessToken(AccessToken, token.ValidTo);
         }    

      }
```

and then create authentication class that returns the **TokenCredentials**

**Authentication.cs >>**

```csharp
using Azure.Core;
using Microsoft.Identity.Client;
using System.IdentityModel.Tokens.Jwt;

namespace Security
{
    internal class Authentication
    {       
        public static string clientId = "Service Principal Client Id";
        private static string[] scopes = ["https://graph.microsoft.com/.default"];
        private static string Authority = "https://login.microsoftonline.com/organizations";
        private static string RedirectURI = "http://localhost";

        public async static Task<TokenCredential> ReturnAuthenticationResult()
        {
            string AccessToken;
            PublicClientApplicationBuilder PublicClientAppBuilder =
                PublicClientApplicationBuilder.Create(clientId)
                .WithAuthority(Authority)
                .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
                .WithRedirectUri(RedirectURI);

            IPublicClientApplication PublicClientApplication = PublicClientAppBuilder.Build();
            var accounts = await PublicClientApplication.GetAccountsAsync();
            AuthenticationResult result;

            try
            {

                result = await PublicClientApplication.AcquireTokenSilent(scopes, accounts.First())
                                 .ExecuteAsync()
                                 .ConfigureAwait(false);

            }
            catch
            {
                result = await PublicClientApplication.AcquireTokenInteractive(scopes)
                                 .ExecuteAsync()
                                 .ConfigureAwait(false);

            }
            
             JwtSecurityToken token = new JwtSecurityToken(result.AccessToken);
            AccessTokenCredential tokenCredential = new AccessTokenCredential(result.AccessToken);

            return tokenCredential;

        }

    }
}
```

Next, we send a prompt to the model asking about **oranges**

```csharp
AgentResponse agentResponse = await agent_.RunAsync("What are oranges ?");
Console.WriteLine(agentResponse);
```

And the purview would block the response

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/07a96f60-cd52-470b-acbf-6cf79cca72f0.png align="center")

If the prompt is about **grapes** there will be a relevant response

```csharp
AgentResponse agentResponse = await agent_.RunAsync("What are grapes ?");
Console.WriteLine(agentResponse);
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/abf0eda3-4b35-4d1b-921a-8d8858b882bd.png align="center")

### Execution >>

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/200715ad-5472-4cf4-a05a-2a09b8774cec.gif align="center")

### Conclusion >>

To wrap it up , a good AI ecosystem success shouldn't be based only by what an AI agent ***can*** do, but also by what it ***should*** be allowed to do. Microsoft Purview helps bridge that gap by bringing governance directly into the AI application lifecycle.

Microsoft Purview and its seamless integration with the Microsoft Agent Framework, provides a powerful foundation for building AI agents that are not only intelligent but also secure, compliant, and enterprise-ready. Governance shouldn't be an afterthought it should be part of the architecture from day one.

Thanks for reading !!
