From 1e4087f479d45a2491901db23e7de8c8e1be1215 Mon Sep 17 00:00:00 2001 From: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:46:42 -0800 Subject: [PATCH 1/6] Document mTLS PoP usage with managed identities Added documentation for using managed identities with mTLS proof-of-possession in MSAL.NET, including code examples and installation instructions. --- docs/msi_v2/how_to_mtls_pop_with_msi.md | 172 ++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 docs/msi_v2/how_to_mtls_pop_with_msi.md diff --git a/docs/msi_v2/how_to_mtls_pop_with_msi.md b/docs/msi_v2/how_to_mtls_pop_with_msi.md new file mode 100644 index 0000000000..1cf2f05d21 --- /dev/null +++ b/docs/msi_v2/how_to_mtls_pop_with_msi.md @@ -0,0 +1,172 @@ +--- +title: Use managed identities with mTLS proof-of-possession (preview) +description: Learn how to use managed identity with mTLS proof-of-possession tokens in MSAL.NET. +ms.service: entra-id +ms.subservice: develop +ms.topic: conceptual +ms.date: 11/17/2025 +--- + +# Use managed identities with mTLS proof-of-possession (internal microsoft only - preview) + +> [!IMPORTANT] +> mTLS proof-of-possession (mTLS PoP) for managed identities is currently in internal preview. +> +> To use `WithMtlsProofOfPosession`, you must add the package +> [`Microsoft.Identity.Client.MtlsPop`](https://www.nuget.org/packages/Microsoft.Identity.Client.MtlsPop) (for example, version `4.79.1-preview`). +> +> The resource (API) must be configured to accept mTLS PoP tokens and validate the certificate bound to the token. + +mTLS PoP builds directly on top of the existing managed identity experience: + +- You still use `ManagedIdentityApplicationBuilder`. +- You still call `AcquireTokenForManagedIdentity`. + +The only changes are: + +- Build Managed Identity app [using MSAL](https://learn.microsoft.com/en-us/entra/msal/dotnet/advanced/managed-identity). +- Add the MtlsPoP package. +- Add `.WithMtlsProofOfPosession()` when acquiring the token. +- Use the returned binding certificate when calling the API over mTLS. + +Below we show the current (Bearer) code first, then the new (mTLS PoP) version, using Microsoft Graph as the example API. + +## 1. Add the MtlsPoP package + +Install the preview package alongside `Microsoft.Identity.Client`: + +```bash +dotnet add package Microsoft.Identity.Client.MtlsPop --version 4.79.1-preview +``` + +This package: + +- exposes the `WithMtlsProofOfPosession()` extension, and +- brings in a native dependency used to attest managed identity keys (for example KeyGuard keys) via Microsoft Azure Attestation (MAA). + +--- + +## 2. System-assigned managed identity – from Bearer to mTLS PoP (Graph) + +### Current experience – Bearer (Graph) + +```csharp +// System-assigned managed identity +IManagedIdentityApplication mi = + ManagedIdentityApplicationBuilder + .Create(ManagedIdentityId.SystemAssigned) + .Build(); + +// Microsoft Graph as the target API +const string graphScope = "https://graph.microsoft.com/"; + +AuthenticationResult result = await mi + .AcquireTokenForManagedIdentity(graphScope) + .ExecuteAsync() + .ConfigureAwait(false); + +// result.AccessToken is a Bearer token +// result.TokenType == "Bearer" +``` + +### New experience – mTLS PoP (Graph) + +```csharp +// System-assigned managed identity +IManagedIdentityApplication mi = + ManagedIdentityApplicationBuilder + .Create(ManagedIdentityId.SystemAssigned) + .Build(); + +// Microsoft Graph as the target API +const string graphScope = "https://graph.microsoft.com/"; + +AuthenticationResult result = await mi + .AcquireTokenForManagedIdentity(graphScope) + .WithMtlsProofOfPosession() // <-- new API + .ExecuteAsync() + .ConfigureAwait(false); + +// result.TokenType == "mtls_pop" +// result.BindingCertificate is the client cert to use for mTLS +``` + +--- + +## 3. User-assigned managed identity – from Bearer to mTLS PoP (Graph) + +### Current experience – Bearer (Graph) + +```csharp +// User-assigned managed identity +IManagedIdentityApplication mi = + ManagedIdentityApplicationBuilder + .Create(ManagedIdentityId.WithUserAssignedClientId(userAssignedClientId)) + .Build(); + +// Microsoft Graph as the target API +const string graphScope = "https://graph.microsoft.com/"; + +AuthenticationResult result = await mi + .AcquireTokenForManagedIdentity(graphScope) + .ExecuteAsync() + .ConfigureAwait(false); + +// result.TokenType == "Bearer" +``` + +### New experience – mTLS PoP (Graph) + +```csharp +// User-assigned managed identity +IManagedIdentityApplication mi = + ManagedIdentityApplicationBuilder + .Create(ManagedIdentityId.WithUserAssignedClientId(userAssignedClientId)) + .Build(); + +// Microsoft Graph as the target API +const string graphScope = "https://graph.microsoft.com/"; + +AuthenticationResult result = await mi + .AcquireTokenForManagedIdentity(graphScope) + .WithMtlsProofOfPosession() // <-- new API + .ExecuteAsync() + .ConfigureAwait(false); + +// result.TokenType == "mtls_pop" +``` + +--- + +## 4. Call Microsoft Graph with an mTLS PoP token + +Once you have an `AuthenticationResult` from `WithMtlsProofOfPosession()`: + +- `result.TokenType` will be `"mtls_pop"`. +- `result.BindingCertificate` is the certificate that the token is bound to. + +Use that certificate for the mTLS handshake, and send the token in the `Authorization` header: + +```csharp +// Use the certificate returned by MSAL for the mTLS handshake +using var handler = new HttpClientHandler(); +handler.ClientCertificates.Add(result.BindingCertificate); + +// Create an HttpClient that uses mTLS +using var httpClient = new HttpClient(handler); + +// Example: call Microsoft Graph +var request = new HttpRequestMessage( + HttpMethod.Get, + "https://graph.microsoft.com/v1.0/me"); + +// Use the token type and token from MSAL +request.Headers.Authorization = + new AuthenticationHeaderValue(result.TokenType, result.AccessToken); +// result.TokenType == "mtls_pop" + +HttpResponseMessage response = await httpClient.SendAsync(request); +response.EnsureSuccessStatusCode(); +``` + +> In production, you should cache `HttpClient` instances per certificate to avoid socket exhaustion. From f2914884c911e680e060cf41462fca447719ca07 Mon Sep 17 00:00:00 2001 From: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:05:27 -0800 Subject: [PATCH 2/6] Fix typos in mTLS PoP documentation --- docs/msi_v2/how_to_mtls_pop_with_msi.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/msi_v2/how_to_mtls_pop_with_msi.md b/docs/msi_v2/how_to_mtls_pop_with_msi.md index 1cf2f05d21..3e18f0e101 100644 --- a/docs/msi_v2/how_to_mtls_pop_with_msi.md +++ b/docs/msi_v2/how_to_mtls_pop_with_msi.md @@ -12,7 +12,7 @@ ms.date: 11/17/2025 > [!IMPORTANT] > mTLS proof-of-possession (mTLS PoP) for managed identities is currently in internal preview. > -> To use `WithMtlsProofOfPosession`, you must add the package +> To use `WithMtlsProofOfPossession`, you must add the package > [`Microsoft.Identity.Client.MtlsPop`](https://www.nuget.org/packages/Microsoft.Identity.Client.MtlsPop) (for example, version `4.79.1-preview`). > > The resource (API) must be configured to accept mTLS PoP tokens and validate the certificate bound to the token. @@ -26,7 +26,7 @@ The only changes are: - Build Managed Identity app [using MSAL](https://learn.microsoft.com/en-us/entra/msal/dotnet/advanced/managed-identity). - Add the MtlsPoP package. -- Add `.WithMtlsProofOfPosession()` when acquiring the token. +- Add `.WithMtlsProofOfPossession()` when acquiring the token. - Use the returned binding certificate when calling the API over mTLS. Below we show the current (Bearer) code first, then the new (mTLS PoP) version, using Microsoft Graph as the example API. @@ -41,7 +41,7 @@ dotnet add package Microsoft.Identity.Client.MtlsPop --version 4.79.1-preview This package: -- exposes the `WithMtlsProofOfPosession()` extension, and +- exposes the `WithMtlsProofOfPossession()` extension, and - brings in a native dependency used to attest managed identity keys (for example KeyGuard keys) via Microsoft Azure Attestation (MAA). --- @@ -65,8 +65,7 @@ AuthenticationResult result = await mi .ExecuteAsync() .ConfigureAwait(false); -// result.AccessToken is a Bearer token -// result.TokenType == "Bearer" +// result.AccessToken is a Bearer token (result.TokenType == "Bearer") ``` ### New experience – mTLS PoP (Graph) @@ -83,7 +82,7 @@ const string graphScope = "https://graph.microsoft.com/"; AuthenticationResult result = await mi .AcquireTokenForManagedIdentity(graphScope) - .WithMtlsProofOfPosession() // <-- new API + .WithMtlsProofOfPossession() // <-- new API .ExecuteAsync() .ConfigureAwait(false); @@ -129,18 +128,19 @@ const string graphScope = "https://graph.microsoft.com/"; AuthenticationResult result = await mi .AcquireTokenForManagedIdentity(graphScope) - .WithMtlsProofOfPosession() // <-- new API + .WithMtlsProofOfPossession() // <-- new API .ExecuteAsync() .ConfigureAwait(false); // result.TokenType == "mtls_pop" +// result.BindingCertificate is the certificate that the token is bound to. ``` --- ## 4. Call Microsoft Graph with an mTLS PoP token -Once you have an `AuthenticationResult` from `WithMtlsProofOfPosession()`: +Once you have an `AuthenticationResult` from `WithMtlsProofOfPossession()`: - `result.TokenType` will be `"mtls_pop"`. - `result.BindingCertificate` is the certificate that the token is bound to. @@ -153,6 +153,7 @@ using var handler = new HttpClientHandler(); handler.ClientCertificates.Add(result.BindingCertificate); // Create an HttpClient that uses mTLS +// Note: In production, cache HttpClient instances per certificate to avoid socket exhaustion using var httpClient = new HttpClient(handler); // Example: call Microsoft Graph From 229444969eab56b2996ad5ba1b1ebaff760ab4f8 Mon Sep 17 00:00:00 2001 From: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:11:08 -0800 Subject: [PATCH 3/6] Update docs/msi_v2/how_to_mtls_pop_with_msi.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/msi_v2/how_to_mtls_pop_with_msi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/msi_v2/how_to_mtls_pop_with_msi.md b/docs/msi_v2/how_to_mtls_pop_with_msi.md index 3e18f0e101..1ad62bbd34 100644 --- a/docs/msi_v2/how_to_mtls_pop_with_msi.md +++ b/docs/msi_v2/how_to_mtls_pop_with_msi.md @@ -7,7 +7,7 @@ ms.topic: conceptual ms.date: 11/17/2025 --- -# Use managed identities with mTLS proof-of-possession (internal microsoft only - preview) +# Use managed identities with mTLS proof-of-possession (internal Microsoft only - preview) > [!IMPORTANT] > mTLS proof-of-possession (mTLS PoP) for managed identities is currently in internal preview. From 6d48ff30bc67651e2cfaed114636ddd1de3446de Mon Sep 17 00:00:00 2001 From: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:38:35 -0800 Subject: [PATCH 4/6] Improve mTLS HttpClient handling and example usage Refactor mTLS HttpClient creation and usage for better performance and clarity. --- docs/msi_v2/how_to_mtls_pop_with_msi.md | 91 ++++++++++++++++++------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/docs/msi_v2/how_to_mtls_pop_with_msi.md b/docs/msi_v2/how_to_mtls_pop_with_msi.md index 1ad62bbd34..5e6e64deb2 100644 --- a/docs/msi_v2/how_to_mtls_pop_with_msi.md +++ b/docs/msi_v2/how_to_mtls_pop_with_msi.md @@ -145,29 +145,74 @@ Once you have an `AuthenticationResult` from `WithMtlsProofOfPossession()`: - `result.TokenType` will be `"mtls_pop"`. - `result.BindingCertificate` is the certificate that the token is bound to. -Use that certificate for the mTLS handshake, and send the token in the `Authorization` header: +In production, you should reuse `HttpClient` instances rather than creating a new one per request. The example below caches `HttpClient` instances **per binding certificate**: ```csharp -// Use the certificate returned by MSAL for the mTLS handshake -using var handler = new HttpClientHandler(); -handler.ClientCertificates.Add(result.BindingCertificate); - -// Create an HttpClient that uses mTLS -// Note: In production, cache HttpClient instances per certificate to avoid socket exhaustion -using var httpClient = new HttpClient(handler); - -// Example: call Microsoft Graph -var request = new HttpRequestMessage( - HttpMethod.Get, - "https://graph.microsoft.com/v1.0/me"); - -// Use the token type and token from MSAL -request.Headers.Authorization = - new AuthenticationHeaderValue(result.TokenType, result.AccessToken); -// result.TokenType == "mtls_pop" - -HttpResponseMessage response = await httpClient.SendAsync(request); -response.EnsureSuccessStatusCode(); -``` +using System; +using System.Collections.Concurrent; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Identity.Client; + +// Cache HttpClient instances per binding certificate (thumbprint) +public static class MtlsHttpClientFactory +{ + private static readonly ConcurrentDictionary s_httpClients = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + public static HttpClient GetClient(X509Certificate2 bindingCertificate) + { + if (bindingCertificate is null) + { + throw new ArgumentNullException(nameof(bindingCertificate)); + } + + return s_httpClients.GetOrAdd(bindingCertificate.Thumbprint, _ => + { + var handler = new HttpClientHandler(); + + // Attach the binding certificate so the connection uses mTLS + handler.ClientCertificates.Add(bindingCertificate); + + // HttpClient is intentionally not disposed here; it is reused for this certificate. + var client = new HttpClient(handler, disposeHandler: true); + // Optionally configure defaults such as Timeout, BaseAddress, etc. + return client; + }); + } +} + +public static class GraphCaller +{ + public static async Task CallGraphWithMtlsPopAsync( + AuthenticationResult result, + CancellationToken cancellationToken = default) + { + if (result is null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Get or create an HttpClient that uses the binding certificate for mTLS + HttpClient httpClient = MtlsHttpClientFactory.GetClient(result.BindingCertificate); + + using var request = new HttpRequestMessage( + HttpMethod.Get, + "https://graph.microsoft.com/v1.0/me"); + + // Use the token type and token from MSAL (TokenType will be "mtls_pop") + request.Headers.Authorization = + new AuthenticationHeaderValue(result.TokenType, result.AccessToken); + + HttpResponseMessage response = await httpClient + .SendAsync(request, cancellationToken) + .ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + // Handle the response body as needed + } +} -> In production, you should cache `HttpClient` instances per certificate to avoid socket exhaustion. From dfaeff1ae3b1b97bc8147007912f02828bdde1a5 Mon Sep 17 00:00:00 2001 From: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:19:25 -0800 Subject: [PATCH 5/6] Revise mTLS PoP documentation for MSAL.NET Updated the documentation for using managed identities with mTLS proof-of-possession in MSAL.NET, including changes to titles, descriptions, and code examples. --- docs/msi_v2/how_to_mtls_pop_with_msi.md | 265 +++++++++++++++++++----- 1 file changed, 211 insertions(+), 54 deletions(-) diff --git a/docs/msi_v2/how_to_mtls_pop_with_msi.md b/docs/msi_v2/how_to_mtls_pop_with_msi.md index 5e6e64deb2..b6a813f711 100644 --- a/docs/msi_v2/how_to_mtls_pop_with_msi.md +++ b/docs/msi_v2/how_to_mtls_pop_with_msi.md @@ -1,22 +1,41 @@ --- -title: Use managed identities with mTLS proof-of-possession (preview) -description: Learn how to use managed identity with mTLS proof-of-possession tokens in MSAL.NET. +title: how to use mTLS proof-of-possession in MSAL(preview) +description: Learn how to acquire mTLS proof-of-possession tokens in MSAL.NET. ms.service: entra-id ms.subservice: develop ms.topic: conceptual ms.date: 11/17/2025 --- -# Use managed identities with mTLS proof-of-possession (internal Microsoft only - preview) +# Use mTLS proof-of-possession (mTLS PoP) tokens in MSAL.NET (internal Microsoft only - preview) > [!IMPORTANT] -> mTLS proof-of-possession (mTLS PoP) for managed identities is currently in internal preview. +> mTLS proof-of-possession (mTLS PoP) support in MSAL.NET is currently in internal preview. > -> To use `WithMtlsProofOfPossession`, you must add the package -> [`Microsoft.Identity.Client.MtlsPop`](https://www.nuget.org/packages/Microsoft.Identity.Client.MtlsPop) (for example, version `4.79.1-preview`). +> - For **managed identities**, `WithMtlsProofOfPossession` is enabled by the +> [`Microsoft.Identity.Client.MtlsPop`](https://www.nuget.org/packages/Microsoft.Identity.Client.MtlsPop) package (preview). +> - For **confidential clients** (SNI and FIC flows), `WithMtlsProofOfPossession` is exposed directly by the MSAL.NET preview package and does **not** require the additional MtlsPoP package. > > The resource (API) must be configured to accept mTLS PoP tokens and validate the certificate bound to the token. +## Overview + +mTLS PoP builds directly on top of existing MSAL experiences: + +- **Managed identity** (system-assigned or user-assigned) via `ManagedIdentityApplicationBuilder` (**requires** the MtlsPoP package). +- **Confidential clients** using **SN/I certificates** via `WithCertificate(...)` (no extra package needed). +- **Federated identity credentials (FIC)** via `WithClientAssertion(...)` (no extra package needed). + +Across all these scenarios: + +- You still use the same MSAL builders and token acquisition methods. +- You call `.WithMtlsProofOfPossession()` on the token request. +- You use the returned `AuthenticationResult.BindingCertificate` when calling the API over mTLS. + +--- + +## 1. Use managed identities with mTLS proof-of-possession + mTLS PoP builds directly on top of the existing managed identity experience: - You still use `ManagedIdentityApplicationBuilder`. @@ -24,16 +43,13 @@ mTLS PoP builds directly on top of the existing managed identity experience: The only changes are: -- Build Managed Identity app [using MSAL](https://learn.microsoft.com/en-us/entra/msal/dotnet/advanced/managed-identity). -- Add the MtlsPoP package. +- Add the MtlsPoP package (managed identity only). - Add `.WithMtlsProofOfPossession()` when acquiring the token. - Use the returned binding certificate when calling the API over mTLS. -Below we show the current (Bearer) code first, then the new (mTLS PoP) version, using Microsoft Graph as the example API. +### 1.1 Add the MtlsPoP package (managed identity only) -## 1. Add the MtlsPoP package - -Install the preview package alongside `Microsoft.Identity.Client`: +For **managed identity** scenarios, install the latest preview package alongside `Microsoft.Identity.Client`: ```bash dotnet add package Microsoft.Identity.Client.MtlsPop --version 4.79.1-preview @@ -41,101 +57,242 @@ dotnet add package Microsoft.Identity.Client.MtlsPop --version 4.79.1-preview This package: -- exposes the `WithMtlsProofOfPossession()` extension, and +- exposes the `WithMtlsProofOfPossession()` extension for managed identity flows, and - brings in a native dependency used to attest managed identity keys (for example KeyGuard keys) via Microsoft Azure Attestation (MAA). ---- - -## 2. System-assigned managed identity – from Bearer to mTLS PoP (Graph) +### 1.2 Current experience – Bearer (Graph) -### Current experience – Bearer (Graph) +The following example shows how to use either a user-assigned or system-assigned managed identity. ```csharp -// System-assigned managed identity +using System.Threading.Tasks; +using Microsoft.Identity.Client; + +// Choose the appropriate managed identity: +// - For user-assigned MI: ManagedIdentityId.WithUserAssignedClientId(userAssignedClientId) +// - For system-assigned MI: ManagedIdentityId.SystemAssigned + IManagedIdentityApplication mi = ManagedIdentityApplicationBuilder - .Create(ManagedIdentityId.SystemAssigned) + .Create( + ManagedIdentityId.WithUserAssignedClientId(userAssignedClientId) + // or: ManagedIdentityId.SystemAssigned + ) .Build(); // Microsoft Graph as the target API -const string graphScope = "https://graph.microsoft.com/"; +const string graphResource = "https://graph.microsoft.com/"; -AuthenticationResult result = await mi - .AcquireTokenForManagedIdentity(graphScope) +AuthenticationResult bearerResult = await mi + .AcquireTokenForManagedIdentity(graphResource) .ExecuteAsync() .ConfigureAwait(false); -// result.AccessToken is a Bearer token (result.TokenType == "Bearer") +// bearerResult.AccessToken is a Bearer token (bearerResult.TokenType == "Bearer") ``` -### New experience – mTLS PoP (Graph) +### 1.3 New experience – mTLS PoP (Graph) ```csharp -// System-assigned managed identity +using System.Threading.Tasks; +using Microsoft.Identity.Client; + +// Choose the appropriate managed identity: +// - For user-assigned MI: ManagedIdentityId.WithUserAssignedClientId(userAssignedClientId) +// - For system-assigned MI: ManagedIdentityId.SystemAssigned + IManagedIdentityApplication mi = ManagedIdentityApplicationBuilder - .Create(ManagedIdentityId.SystemAssigned) + .Create( + ManagedIdentityId.WithUserAssignedClientId(userAssignedClientId) + // or: ManagedIdentityId.SystemAssigned + ) .Build(); // Microsoft Graph as the target API -const string graphScope = "https://graph.microsoft.com/"; +const string graphResource = "https://graph.microsoft.com/"; -AuthenticationResult result = await mi - .AcquireTokenForManagedIdentity(graphScope) +AuthenticationResult mtlsPopResult = await mi + .AcquireTokenForManagedIdentity(graphResource) .WithMtlsProofOfPossession() // <-- new API .ExecuteAsync() .ConfigureAwait(false); -// result.TokenType == "mtls_pop" -// result.BindingCertificate is the client cert to use for mTLS +// mtlsPopResult.TokenType == "mtls_pop" +// mtlsPopResult.BindingCertificate is the client cert to use for mTLS ``` --- -## 3. User-assigned managed identity – from Bearer to mTLS PoP (Graph) +## 2. Use SNI certificate + confidential client app for mTLS PoP + +This section shows how to use mTLS PoP with a **confidential client application** that authenticates using an **SN/I certificate**. -### Current experience – Bearer (Graph) +You configure: + +- An SN/I certificate on the app via `WithCertificate(...)`, +- A region via `WithAzureRegion("east us")`, and +- `WithMtlsProofOfPossession()` on the token request. + +The resulting token is bound to a certificate exposed as `AuthenticationResult.BindingCertificate`, which you then use to call Microsoft Graph over mTLS. + +> [!NOTE] +> For SNI-based confidential clients, mTLS PoP is provided by the MSAL.NET preview package itself. +> You do **not** need the `Microsoft.Identity.Client.MtlsPop` package for this scenario. + +### 2.1 Current experience – Bearer with SN/I certificate ```csharp -// User-assigned managed identity -IManagedIdentityApplication mi = - ManagedIdentityApplicationBuilder - .Create(ManagedIdentityId.WithUserAssignedClientId(userAssignedClientId)) +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Microsoft.Identity.Client; + +X509Certificate2 clientCertificate = LoadSnICertificate(); + +IConfidentialClientApplication app = + ConfidentialClientApplicationBuilder + .Create(clientId) + .WithAuthority($"https://login.microsoftonline.com/{tenantId}") + .WithAzureRegion("east us") // Region example; required for SNI + .WithCertificate(clientCertificate, sendX5C: true) .Build(); -// Microsoft Graph as the target API -const string graphScope = "https://graph.microsoft.com/"; +string[] scopes = new[] { "https://graph.microsoft.com/.default" }; -AuthenticationResult result = await mi - .AcquireTokenForManagedIdentity(graphScope) +AuthenticationResult result = await app + .AcquireTokenForClient(scopes) .ExecuteAsync() .ConfigureAwait(false); -// result.TokenType == "Bearer" +// result.TokenType will return "Bearer" ``` -### New experience – mTLS PoP (Graph) +### 2.2 New experience – mTLS PoP with SN/I certificate ```csharp -// User-assigned managed identity -IManagedIdentityApplication mi = - ManagedIdentityApplicationBuilder - .Create(ManagedIdentityId.WithUserAssignedClientId(userAssignedClientId)) +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Identity.Client; + +X509Certificate2 clientCertificate = LoadSnICertificate(); + +IConfidentialClientApplication app = + ConfidentialClientApplicationBuilder + .Create(clientId) + .WithAuthority($"https://login.microsoftonline.com/{tenantId}") + .WithAzureRegion("east us") // Required for SNI + .WithCertificate(clientCertificate, sendX5C: true) .Build(); -// Microsoft Graph as the target API -const string graphScope = "https://graph.microsoft.com/"; +string[] scopes = new[] { "https://graph.microsoft.com/.default" }; -AuthenticationResult result = await mi - .AcquireTokenForManagedIdentity(graphScope) - .WithMtlsProofOfPossession() // <-- new API +AuthenticationResult result = await app + .AcquireTokenForClient(scopes) + .WithMtlsProofOfPossession() // <-- new mTLS PoP API .ExecuteAsync() .ConfigureAwait(false); -// result.TokenType == "mtls_pop" +// result.TokenType will return "mtls_pop" // result.BindingCertificate is the certificate that the token is bound to. +// For SN/I flows, this is the same certificate passed to WithCertificate. + +await GraphCaller.CallGraphWithMtlsPopAsync(result, CancellationToken.None); ``` +> `LoadSnICertificate()` is an app-specific helper that loads your SN/I certificate +> (for example, from the CurrentUser/My store or a Key Vault-backed certificate). + +--- + +## 3. Use mTLS PoP with federated identity credentials (FIC) + +You can also combine mTLS proof-of-possession with **federated identity credentials (FIC)**. + +In this case, the confidential client: + +- Uses `WithClientAssertion(...)` instead of a secret or certificate. +- Provides a `ClientSignedAssertion` that includes: + - the FIC assertion (`Assertion`), and + - the certificate used for mTLS binding (`TokenBindingCertificate`). +- Acquires tokens with `WithMtlsProofOfPossession()`. + +> [!NOTE] +> As with the SNI scenario, FIC-based confidential clients use the `WithMtlsProofOfPossession()` support built into the MSAL.NET preview package and do **not** require the MtlsPoP package. + +```csharp +using System; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.AppConfig; // AssertionRequestOptions, ClientSignedAssertion + +// Application-specific helper that creates the FIC assertion (JWT) +private static string CreateFicJwt(AssertionRequestOptions options, X509Certificate2 bindingCertificate) +{ + // TODO: create and sign a JWT according to your FIC configuration + // (issuer, subject, audience, claims, expiry, etc.) + return ""; +} + +// Delegate used by MSAL to obtain a client assertion + binding certificate +private static Func> +CreateFicAssertion(X509Certificate2 bindingCertificate) +{ + return (options, cancellationToken) => + { + string jwt = CreateFicJwt(options, bindingCertificate); + + return Task.FromResult(new ClientSignedAssertion + { + Assertion = jwt, + TokenBindingCertificate = bindingCertificate + }); + }; +} + +public static async Task AcquireTokenWithFicAndMtlsPopAsync( + string clientId, + string tenantId, + X509Certificate2 bindingCertificate, + CancellationToken cancellationToken = default) +{ + if (bindingCertificate is null) + { + throw new ArgumentNullException(nameof(bindingCertificate)); + } + + // Configure the confidential client to use FIC via client assertion + IConfidentialClientApplication app = + ConfidentialClientApplicationBuilder + .Create(clientId) + .WithAuthority($"https://login.microsoftonline.com/{tenantId}") + .WithAzureRegion("east us") // required for SNI + .WithClientAssertion(CreateFicAssertion(bindingCertificate)) + .Build(); + + string[] scopes = new[] { "https://graph.microsoft.com/.default" }; + + AuthenticationResult result = await app + .AcquireTokenForClient(scopes) + .WithMtlsProofOfPossession() // <-- enable mTLS PoP on this flow + .ExecuteAsync(cancellationToken) + .ConfigureAwait(false); + + // result.TokenType == "mtls_pop" + // result.BindingCertificate matches the certificate used in the assertion + // (TokenBindingCertificate). You can now call Graph over mTLS using the + // shared helper in "Call Microsoft Graph with an mTLS PoP token": + await GraphCaller.CallGraphWithMtlsPopAsync(result, cancellationToken); +} +``` + +> In both managed identity and SN/I + FIC cases, the `BindingCertificate` and `AccessToken` +> from the `AuthenticationResult` can be reused: +> - as the certificate for the mTLS connection to the resource, and +> - as the access token you send in the `Authorization` header. + --- ## 4. Call Microsoft Graph with an mTLS PoP token @@ -215,4 +372,4 @@ public static class GraphCaller // Handle the response body as needed } } - +``` From d7a50a56b683b67c99e7c13c7d0afb684a4e351e Mon Sep 17 00:00:00 2001 From: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:25:35 -0800 Subject: [PATCH 6/6] Update docs/msi_v2/how_to_mtls_pop_with_msi.md Co-authored-by: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com> --- docs/msi_v2/how_to_mtls_pop_with_msi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/msi_v2/how_to_mtls_pop_with_msi.md b/docs/msi_v2/how_to_mtls_pop_with_msi.md index b6a813f711..d82c7a0626 100644 --- a/docs/msi_v2/how_to_mtls_pop_with_msi.md +++ b/docs/msi_v2/how_to_mtls_pop_with_msi.md @@ -45,7 +45,7 @@ The only changes are: - Add the MtlsPoP package (managed identity only). - Add `.WithMtlsProofOfPossession()` when acquiring the token. -- Use the returned binding certificate when calling the API over mTLS. +- Use the binding certificate in `AuthenticationResult` when calling the API over mTLS. ### 1.1 Add the MtlsPoP package (managed identity only)