What is S2S token binding?
+A standard OAuth access token is a bearer token: whoever holds it can use it. If it leaks β + via a log, a proxy, or an SSRF β it can be replayed from anywhere until it expires.
+Token binding issues an access token cryptographically bound to a private key the caller holds,
+ and the token is presented over mutual-TLS Proof-of-Possession (mTLS PoP). The caller must
+ prove possession of that key, so a leaked token is useless without it. The resource matches the presented
+ binding certificate to the token's cnf (confirmation) claim.
The two tokens, side by side
+Steal it β replay anywhere β β works for the attacker. This is the risk.
Steal it β replay elsewhere β β rejected, no bound key. Sender-constrained.
Why it matters
+| Bearer | Token-bound (mTLS PoP) | |
|---|---|---|
| Replay a leaked token | Works for the attacker | Rejected β no bound key |
| Replay from another machine | Works | Key is sender-constrained |
| Credential type | Portable secret | Sender-constrained |
Token binding is a core Secure Future Initiative (SFI) pattern: it removes token replay as a + class of attack for high-value S2S calls.
+Two credential sources (and FIC composes either)
+The binding works the same way at the wire; what differs is where the binding key lives.
+πͺͺ SN+I (certificate) GA
+The caller is an app registration authenticating with a certificate + (Subject Name + Issuer). The token is bound to that certificate β no VM or KeyGuard required.
+Optionally, you can import the certificate and store its private key in KeyGuard (VBS-isolated, non-exportable) for hardware-backed protection of the binding key.
+ +βοΈ Managed Identity Public preview
+The caller is the managed identity. The binding key is generated in + VBS / KeyGuard on a Trusted Launch / Confidential VM and attested by + Microsoft Azure Attestation (MAA).
+π FIC (Federated Identity Credential)
+An app registration uses a federated credential β an SN+I certificate or a + Managed Identity β as its client assertion. FIC composes the two sources above.
+Supported scenarios
+| Scenario | Credential | Host | Status |
|---|---|---|---|
| SN+I (certificate client credentials) | App certificate (Subject Name + Issuer) | +Any host β no VM/KeyGuard | GA |
| Pure Managed Identity | Managed Identity (SAMI/UAMI) | +Trusted Launch / Confidential VM with VBS/KeyGuard | Public preview |
| FIC (app registration) | Federated credential β SN+I or Managed Identity | +Per the underlying credential | SN+I: GA Β· MSI: preview |
Configure Token Binding on a Trusted Launch / Confidential VM
+Prepare a Windows VM so a Managed Identity can mint bound credentials:
+-
+
- Create a Trusted Launch VM + or Confidential VM + (Secure Boot + vTPM on). +
- Assign a Managed Identity (SAMI or UAMI) and grant it RBAC on the target (e.g. Key Vault Secrets User). +
- Enable VBS + KeyGuard inside the guest, then reboot: +
reg add "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard" /v "EnableVirtualizationBasedSecurity" /t REG_DWORD /d 1 /f
+reg add "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard" /v "RequirePlatformSecurityFeatures" /t REG_DWORD /d 1 /f
+reg add "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity" /v "Enabled" /t REG_DWORD /d 1 /f
+Restart-Computer -Force
+ -
+
- Install .NET 8, the VC++ x64 redistributable, and the MSAL packages
+ (
Microsoft.Identity.Client+Microsoft.Identity.Client.KeyAttestation).
+
centraluseuap, eastus2euap)
+ and the PROD region westcentralus. More PROD regions are lighting up as the rollout continues.SDK Support: MSAL .NET the engine
+MSAL is the engine for all three credential sources. It acquires the bound token and the binding certificate.
+<ItemGroup>
+ <PackageReference Include="Microsoft.Identity.Client" Version="4.85.2" />
+ <PackageReference Include="Microsoft.Identity.Client.KeyAttestation" Version="4.85.2" />
+</ItemGroup>
+ WithAttestationSupport() lives in the separate
+ Microsoft.Identity.Client.KeyAttestation package. Without that reference and the matching
+ using, you get CS1061: ... does not contain a definition for 'WithAttestationSupport'.SN+I (certificate) GA
+var app = ConfidentialClientApplicationBuilder
+ .Create("<app-client-id>")
+ .WithCertificate(certificate, sendX5C: true)
+ .WithAzureRegion("westus3")
+ .Build();
+
+AuthenticationResult result = await app
+ .AcquireTokenForClient(new[] { "api://<target-app-id>/.default" })
+ .WithMtlsProofOfPossession()
+ .ExecuteAsync();
+// result.TokenType == "mtls_pop"; result.BindingCertificate is set
+ CertificateOptions { SendCertificateOverMtls = true } and omit
+ WithMtlsProofOfPossession().Pure Managed Identity Public preview
+IManagedIdentityApplication mi = ManagedIdentityApplicationBuilder
+ .Create(ManagedIdentityId.WithUserAssignedClientId("<UAMI-client-id>"))
+ .Build();
+
+AuthenticationResult result = await mi
+ .AcquireTokenForManagedIdentity("https://vault.azure.net")
+ .WithMtlsProofOfPossession()
+ .WithAttestationSupport()
+ .ExecuteAsync();
+// result.TokenType == "mtls_pop"; result.BindingCertificate is set
+
+ FIC (confidential client, MSI as the federated credential)
+Func<AssertionRequestOptions, CancellationToken, Task<ClientSignedAssertion>> assertionDelegate =
+ async (opts, ct) =>
+ {
+ var miResult = await miApp
+ .AcquireTokenForManagedIdentity("api://AzureADTokenExchange")
+ .WithMtlsProofOfPossession()
+ .WithAttestationSupport()
+ .ExecuteAsync(ct);
+
+ return new ClientSignedAssertion
+ {
+ Assertion = miResult.AccessToken,
+ TokenBindingCertificate = miResult.BindingCertificate
+ };
+ };
+
+var cca = ConfidentialClientApplicationBuilder.Create("<app-client-id>")
+ .WithAuthority(new Uri("https://login.microsoftonline.com/<tenant-id>"), validateAuthority: false)
+ .WithAzureRegion("westus3")
+ .WithClientAssertion(assertionDelegate)
+ .Build();
+
+var result = await cca
+ .AcquireTokenForClient(new[] { "https://vault.azure.net/.default" })
+ .WithMtlsProofOfPossession()
+ .ExecuteAsync();
+// When TokenBindingCertificate is supplied, MSAL uses
+// client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-pop
+
+ SDK Support: Microsoft Identity Web config-driven
+Opt in per downstream API with "ProtocolScheme": "MTLS_POP". IDownstreamApi handles
+ acquisition, the binding certificate, and the mTLS call β you write no plumbing.
SN+I (certificate)
+{
+ "AzureAd": {
+ "Instance": "https://login.microsoftonline.com/",
+ "TenantId": "<tenant-id>",
+ "ClientId": "<app-client-id>",
+ "AzureRegion": "westus3",
+ "ClientCredentials": [
+ {
+ "SourceType": "StoreWithDistinguishedName",
+ "CertificateStorePath": "LocalMachine/My",
+ "CertificateDistinguishedName": "CN=<your-cert>"
+ }
+ ],
+ "SendX5C": true
+ },
+ "MyWebApi": {
+ "BaseUrl": "https://mtlsauth.myapi.microsoft.com",
+ "RelativePath": "api/TodoList",
+ "RequestAppToken": true,
+ "Scopes": [ "api://<target-app-id>/.default" ],
+ "ProtocolScheme": "MTLS_POP"
+ }
+}
+
+ Pure Managed Identity
+{
+ "AzureKeyVault": {
+ "BaseUrl": "https://contoso.vault.azure.net/",
+ "RelativePath": "secrets/mysecret?api-version=7.4",
+ "Scopes": [ "https://vault.azure.net/.default" ],
+ "RequestAppToken": true,
+ "ProtocolScheme": "MTLS_POP",
+ "AcquireTokenOptions": {
+ "ManagedIdentity": { "UserAssignedClientId": "<UAMI-client-id>" }
+ }
+ }
+}
+
+ FIC (Managed Identity as a federated credential)
+{
+ "AzureAd": {
+ "Instance": "https://login.microsoftonline.com/",
+ "TenantId": "<tenant-id>",
+ "ClientId": "<app-client-id>",
+ "ClientCapabilities": [ "cp1" ],
+ "ClientCredentials": [
+ {
+ "SourceType": "SignedAssertionFromManagedIdentity",
+ "ManagedIdentityClientId": "<UAMI-client-id>"
+ }
+ ]
+ },
+ "AzureKeyVault": {
+ "BaseUrl": "https://contoso.vault.azure.net/",
+ "Scopes": [ "https://vault.azure.net/.default" ],
+ "RequestAppToken": true,
+ "ProtocolScheme": "MTLS_POP"
+ }
+}
+ ClientCapabilities: [ "cp1" ] advertises support for
+ Continuous Access Evaluation (CAE).Call it (same for every credential source)
+var api = serviceProvider.GetRequiredService<IDownstreamApi>();
+HttpResponseMessage resp = await api.CallApiForAppAsync("AzureKeyVault");
+ SDK Support: Azure Key Vault SDK (AKV) Beta
+The Azure Key Vault SDK clients (SecretClient, CertificateClient, KeyClient) get
+ token binding transparently when you pass a ManagedIdentityCredential (from Azure.Identity):
+ on a supported host it acquires a bound token and the AKV client performs the call over mutual TLS. Token binding is
+ on by default; opt out with DisableMtlsProofOfPossession = true.
Azure.Core is an alpha build); they will change as the feature moves toward GA.<ItemGroup>
+ <!-- BETA / preview packages for the token-binding Key Vault scenario -->
+ <PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.12.0-beta.1" />
+ <PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.10.0-beta.2" />
+ <PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.11.0-beta.2" />
+ <PackageReference Include="Azure.Core" Version="1.60.0-alpha.20260622.10" />
+ <PackageReference Include="Azure.Identity.Broker" Version="1.8.0-beta.1" />
+</ItemGroup>
+ using Azure.Identity;
+using Azure.Security.KeyVault.Secrets;
+
+string keyVaultUrl = "https://<your-kv>.vault.azure.net/";
+string clientId = "<user-assigned-mi-client-id>";
+
+var credential = new ManagedIdentityCredential(
+ new ManagedIdentityCredentialOptions(ManagedIdentityId.FromUserAssignedClientId(clientId))
+ {
+ // Token binding (mTLS PoP) is ON by default. To opt out:
+ // DisableMtlsProofOfPossession = true,
+ Diagnostics = { IsLoggingContentEnabled = true }
+ });
+
+var secretClient = new SecretClient(new Uri(keyVaultUrl), credential);
+KeyVaultSecret secret = await secretClient.GetSecretAsync("<secret-name>");
+ The same credential works with any Azure SDK client (for example BlobServiceClient for Storage).
Call a downstream API over mTLS (Azure Key Vault)
+With raw MSAL you pin result.BindingCertificate to the HttpClient, send the token with the
+ mtls_pop scheme, and include the Key Vault opt-in header.
using var handler = new HttpClientHandler
+{
+ ClientCertificateOptions = ClientCertificateOption.Manual,
+ SslProtocols = SslProtocols.Tls12,
+};
+handler.ClientCertificates.Add(result.BindingCertificate);
+
+using var http = new HttpClient(handler);
+http.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue(result.TokenType, result.AccessToken); // "mtls_pop <token>"
+http.DefaultRequestHeaders.Add("x-ms-tokenboundauth", "true"); // Key Vault opt-in
+
+var akvUrl = "https://<your-kv>.vault.azure.net/secrets/<name>?api-version=7.4";
+using var resp = await http.GetAsync(akvUrl);
+resp.EnsureSuccessStatusCode();
+ mtls_pop scheme and the binding certificate on the
+ channel β never as a plain Bearer token.Supported resources
+| Resource | Scope | Status |
|---|---|---|
| Azure Key Vault | https://vault.azure.net/.default | Supported (requires x-ms-tokenboundauth: true) |
| Microsoft Graph | https://graph.microsoft.com/.default | Verify enforcement for your scenario |
| Your custom web API | api://<app-id>/.default | Supported when validated with MISE |
A resource accepts bound tokens only when it is on the ESTS MtlsTokenBindingEnabledStateResourceAppList
+ and declares support in its app manifest (rp_tbreq, rp_tbtype).
Token acquisition β classic MSI vs a bound token, then the scenarios
+Then vs now: classic Managed Identity is one hop to the local endpoint and hands back a + plain bearer token. A bound token takes several hops (MSAL drives them) so the + token is cryptographically tied to a key that never leaves the machine. The two cards below show each; the runnable + scenarios AβE follow.
+Scenarios: AβE are token theft & replay outcomes β does a stolen token still work from another machine?
+ + +The pipeline β module β endpoint β result
+| # | Stage | Module β endpoint | Result |
|---|---|---|---|
| 1 | Platform metadata | HttpManager β IMDSv2 GET /getPlatformMetadata | client_id, tenant_id, CUID, MAA endpoint |
| 2 | Key + CSR | WinKeyProvider (KeyGuard KSP) | RSA-2048 key + CSR CN={client_id}, DC={tenant_id} |
| 3 | Attest key | PopKeyAttestor β MAA POST /attest/keyguard | attestation_token |
| 4 | Issue credential | HttpManager β IMDSv2 POST /issuecredential | x509 binding cert (~7-day) + regional token URL |
| 5 | Acquire token | HttpManager β ESTS-R POST /oauth2/v2.0/token (mTLS) | mtls_pop token (xms_tb claim) |
| 6 | Return | ManagedIdentityApplication β your app | AuthenticationResult = token + BindingCertificate |
mtls_pop token; unattested compute gets a plain bearer. The binding cert lasts ~7 days and MSAL rotates it ~3 days before expiry. MSI v2 /issuecredential design doc β403 Forbidden from Key Vault is an RBAC result, not a token-binding failure β the mtls_pop token is accepted (auth OK) and only the role (e.g. Key Vault Secrets User) is missing. Token-binding/auth failures surface as 401 (e.g. S2S17001).Check your understanding
+ + +Glossary
+| Term | Meaning |
|---|---|
| mTLS PoP | Mutual-TLS Proof-of-Possession β the caller proves it holds the bound private key during the TLS handshake. |
cnf | JWT "confirmation" claim β holds the thumbprint of the key the token is bound to. |
| SN+I | Subject Name + Issuer β certificate-based client credentials; the token binds to the cert. |
| KeyGuard / VBS | Virtualization-Based Security enclave that stores the bound key so it can't be exported. |
| MAA | Microsoft Azure Attestation β attests the KeyGuard-protected key for the Managed Identity flow. |
| FIC | Federated Identity Credential β an app registration uses an SN+I cert or a Managed Identity as its assertion. |
mtls_pop | The token type / auth scheme for a bound token (vs Bearer). |
| Trusted Launch / CVM | Azure VM security types (Secure Boot + vTPM / confidential) that KeyGuard requires. |
Resources
+-
+
- SN+I Authentication wiki β onboarding & tenant allow-listing for the certificate flow. +
- MSI v2
/issuecredentialhigh-level design (MSAL) β the attestation β ESTS mTLS mechanism.
+ - SN+I client-credentials mTLS PoP β MSAL integration tests. +
- Using mTLS Proof of Possession (MISE) β how-to & troubleshooting. +
- Managed identity with MSAL.NET. +
- Tenant allow-listing / feature questions: tokenbindingfeatureteam@service.microsoft.com +
Interactive guide generated from the S2S Token Binding docset Β· simulated console outputs are illustrative.
+