- name
- msal-mtls-pop-vanilla
- description
- Direct mTLS PoP token acquisition for target resources using Managed Identity or Confidential Client
# MSAL.NET mTLS PoP - Vanilla Flow (Direct Token Acquisition)
This skill covers direct mTLS Proof-of-Possession (PoP) token acquisition for target resources without intermediate token exchanges. Use this when you need to acquire an mTLS PoP token directly for a resource like Microsoft Graph, Azure Key Vault, or custom APIs.
> **Note:** This skill focuses on mTLS PoP-specific APIs and patterns. For general credential setup (certificates, FIC, etc.), see the [Shared Resources](../msal-shared/credential-setup/) for reusable, granularized patterns.
## What is Vanilla Flow?
**Vanilla flow** is a single-step, direct token acquisition from Azure AD for a target resource:
- **One call**: `AcquireTokenForManagedIdentity()` or `AcquireTokenForClient()`
- **No intermediate steps**: Direct to target resource (e.g., `https://graph.microsoft.com`)
- **No "legs"**: This is NOT a multi-step process (do not confuse with FIC two-leg flow)
## Authentication Methods Supported
### 1. System-Assigned Managed Identity (SAMI)
Works in Azure environments only (VM, App Service, Functions, Container Instances, AKS).
### 2. User-Assigned Managed Identity (UAMI)
Can be specified using any of three ID types (all refer to the same identity):
### 3. Confidential Client with Certificate (SNI)
Works anywhere with certificate access (local dev, Azure, on-premises).
## Requirements
- **MSAL.NET**: 4.82.1 minimum
- **NuGet Packages**:
```bash
dotnet add package Microsoft.Identity.Client --version 4.82.1
dotnet add package Microsoft.Identity.Client.KeyAttestation
```
- **Target Framework**: net8.0 recommended
- **Namespaces**:
```csharp
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.AppConfig; // For ManagedIdentityId
using Microsoft.Identity.Client.KeyAttestation; // For WithAttestationSupport()
```
## Quick Start Examples
### SAMI (System-Assigned Managed Identity)
```csharp
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.KeyAttestation;
// Build SAMI app
var app = ManagedIdentityApplicationBuilder.Create(
ManagedIdentityId.SystemAssigned)
.Build();
// Acquire mTLS PoP token with Credential Guard attestation
var result = await app
.AcquireTokenForManagedIdentity("https://graph.microsoft.com")
.WithMtlsProofOfPossession()
.WithAttestationSupport() // ← Credential Guard attestation
.ExecuteAsync();
Console.WriteLine($"Token Type: {result.TokenType}"); // "mtls_pop"
Console.WriteLine($"Certificate Thumbprint: {result.BindingCertificate?.Thumbprint}");
// Configure HttpClient with the binding certificate for mTLS
var handler = new HttpClientHandler();
if (result.BindingCertificate != null)
{
handler.ClientCertificates.Add(result.BindingCertificate);
}
using var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("mtls_pop", result.AccessToken);
// Call Microsoft Graph
var response = await httpClient.GetAsync("https://mtlstb.graph.microsoft.com/v1.0/applications");
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
```
### UAMI by Client ID
```csharp
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.KeyAttestation;
// Build UAMI app with Client ID
var app = ManagedIdentityApplicationBuilder.Create(
ManagedIdentityId.WithUserAssignedClientId("6325cd32-9911-41f3-819c-416cdf9104e7"))
.Build();
// Acquire mTLS PoP token
var result = await app
.AcquireTokenForManagedIdentity("https://vault.azure.net")
.WithMtlsProofOfPossession()
.WithAttestationSupport()
.ExecuteAsync();
// Configure HttpClient with the binding certificate for mTLS
var handler = new HttpClientHandler();
if (result.BindingCertificate != null)
{
handler.ClientCertificates.Add(result.BindingCertificate);
}
using var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("mtls_pop", result.AccessToken);
// Call Azure Key Vault
var response = await httpClient.GetAsync("https://your-vault.vault.azure.net/secrets/my-secret?api-version=7.4");
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
```
### UAMI by Resource ID
```csharp
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.KeyAttestation;
// Build UAMI app with Resource ID (ARM path)
var app = ManagedIdentityApplicationBuilder.Create(
ManagedIdentityId.WithUserAssignedResourceId(
"/subscriptions/c1686c51-b717-4fe0-9af3-24a20a41fb0c/resourcegroups/MSIV2-Testing-MSALNET/providers/Microsoft.ManagedIdentity/userAssignedIdentities/msiv2uami"))
.Build();
// Acquire mTLS PoP token
var result = await app
.AcquireTokenForManagedIdentity("https://storage.azure.com")
.WithMtlsProofOfPossession()
.WithAttestationSupport()
.ExecuteAsync();
Console.WriteLine($"Token Type: {result.TokenType}"); // "mtls_pop"
Console.WriteLine($"Certificate Thumbprint: {result.BindingCertificate?.Thumbprint}");
// Configure HttpClient with the binding certificate for mTLS
var handler = new HttpClientHandler();
if (result.BindingCertificate != null)
{
handler.ClientCertificates.Add(result.BindingCertificate);
}
using var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("mtls_pop", result.AccessToken);
// Call Azure Storage
var response = await httpClient.GetAsync("https://your-storage-account.blob.core.windows.net/?comp=list");
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
```
### UAMI by Object ID
```csharp
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.KeyAttestation;
// Build UAMI app with Object ID (Principal ID)
var app = ManagedIdentityApplicationBuilder.Create(
ManagedIdentityId.WithUserAssignedObjectId("ecb2ad92-3e30-4505-b79f-ac640d069f24"))
.Build();
// Acquire mTLS PoP token
var result = await app
.AcquireTokenForManagedIdentity("https://management.azure.com")
.WithMtlsProofOfPossession()
.WithAttestationSupport()
.ExecuteAsync();
Console.WriteLine($"Token Type: {result.TokenType}"); // "mtls_pop"
Console.WriteLine($"Certificate Thumbprint: {result.BindingCertificate?.Thumbprint}");
// Configure HttpClient with the binding certificate for mTLS
var handler = new HttpClientHandler();
if (result.BindingCertificate != null)
{
handler.ClientCertificates.Add(result.BindingCertificate);
}
using var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("mtls_pop", result.AccessToken);
// Call Azure Resource Manager
var response = await httpClient.GetAsync("https://management.azure.com/subscriptions?api-version=2021-04-01");
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
```
### Confidential Client with Certificate (SNI)
```csharp
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.KeyAttestation;
// Load certificate - see ../msal-shared/credential-setup/certificate-setup.md for details
var cert = GetCertificateFromStore("CN=MyAppCertificate");
// Build Confidential Client with SNI - see ../msal-shared/credential-setup/certificate-sni-setup.md
var app = ConfidentialClientApplicationBuilder
.Create("your-client-id")
.WithAuthority("https://login.microsoftonline.com/your-tenant-id")
.WithAzureRegion("westus3") // Use actual region
.WithCertificate(cert, sendX5c: true) // SNI: send X.509 chain
.Build();
// Acquire mTLS PoP token
var result = await app
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
.WithMtlsProofOfPossession()
.ExecuteAsync();
Console.WriteLine($"Token Type: {result.TokenType}"); // "mtls_pop"
Console.WriteLine($"Binding Certificate matches SNI cert: {result.BindingCertificate.Thumbprint == cert.Thumbprint}");
// Configure HttpClient with the binding certificate for mTLS
var handler = new HttpClientHandler();
if (result.BindingCertificate != null)
{
handler.ClientCertificates.Add(result.BindingCertificate);
}
using var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("mtls_pop", result.AccessToken);
// Call Microsoft Graph
var response = await httpClient.GetAsync("https://mtlstb.graph.microsoft.com/v1.0/applications");
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
```
## Production Helper Classes
This skill includes three production-ready helper classes:
### 1. VanillaMsiMtlsPop.cs
Complete MSI implementation supporting SAMI and all 3 UAMI ID types with Credential Guard attestation.
### 2. MtlsPopTokenAcquirer.cs
Unified token acquisition for both MSI and Confidential Client with attestation support.
### 3. ResourceCaller.cs
Helper for calling protected resources with mTLS PoP tokens.
See the `.cs` files in this directory for complete implementations.
## Usage Pattern
```csharp
// 1. Acquire token with PoP
var result = await app
.AcquireTokenForManagedIdentity("https://graph.microsoft.com")
.WithMtlsProofOfPossession()
.WithAttestationSupport()
.ExecuteAsync();
// 2. Verify PoP token
if (result.TokenType != "mtls_pop")
{
throw new InvalidOperationException("Expected mTLS PoP token");
}
if (result.BindingCertificate == null)
{
throw new InvalidOperationException("BindingCertificate is required for mTLS calls");
}
// 3. Call resource with mTLS binding
using var caller = new ResourceCaller(result);
string response = await caller.CallResourceAsync("https://mtlstb.graph.microsoft.com/v1.0/applications");
```
## Key Points
1. **Vanilla flow is NOT multi-step**: Direct acquisition, no "legs"
2. **Always include `.WithAttestationSupport()`**: Required for Credential Guard in production
3. **SAMI only works in Azure**: Use UAMI or Confidential Client for local development
4. **All 3 UAMI ID types are equivalent**: Use whichever is most convenient
5. **Check `BindingCertificate` for null**: Required for making mTLS calls to target resource
6. **SNI requires `sendX5c: true`**: In `.WithCertificate(cert, sendX5c: true)`
7. **Use actual Azure region**: e.g., "westus3", not placeholders
## Troubleshooting
### mTLS PoP-Specific Issues
| Error/Issue | Solution |
|-------------|----------|
| `ManagedIdentityId` is not defined | Add `using Microsoft.Identity.Client.AppConfig;` |
| `WithMtlsProofOfPossession()` not found | Upgrade to MSAL.NET 4.82.1+ |
| `BindingCertificate` is null | Ensure `.WithMtlsProofOfPossession()` was called before `ExecuteAsync()` |
| `WithAttestationSupport()` not found | Add NuGet: `Microsoft.Identity.Client.KeyAttestation` |
| "Timeout calling IMDS endpoint" (local) | SAMI doesn't work locally. Use UAMI or Confidential Client |
| "Unable to get UAMI token" | Check: UAMI exists, assigned to resource, correct ID type used |
### General Credential and Authentication Issues
For certificate loading, token caching, error handling, and general troubleshooting, see:
- [Certificate Setup](../msal-shared/credential-setup/certificate-setup.md) - Loading certificates from file, store, or Key Vault
- [Certificate SNI Setup](../msal-shared/credential-setup/certificate-sni-setup.md) - SNI configuration details
- [Error Handling Patterns](../msal-shared/patterns/error-handling-patterns.md) - Common error scenarios
- [Troubleshooting](../msal-shared/patterns/troubleshooting.md) - Comprehensive troubleshooting guide
- [Token Caching Strategies](../msal-shared/patterns/token-caching-strategies.md) - Cache management best practices
## Additional Resources
- [Shared Guidance Skill](../msal-mtls-pop-guidance/SKILL.md) - Terminology and conventions
- [FIC Two-Leg Flow Skill](../msal-mtls-pop-fic-two-leg/SKILL.md) - Token exchange scenarios
- [ClientCredentialsMtlsPopTests.cs](../../../tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsMtlsPopTests.cs) - Integration test examples
View on GitHub