Skip to content

[Bug] mTLS PoP — stale cert-bound token served from cache after binding-certificate rotation (AADSTS500181) #6117

Description

@gladjohn

Library version used

latest

.NET version

net 8

Scenario

ConfidentialClient - service to service (AcquireTokenForClient)

Is this a new or an existing app?

The app is in production, and I have upgraded to a new version of MSAL

Issue description and reproduction steps

Bug: mTLS PoP — stale cert-bound token served from cache after binding-certificate rotation (AADSTS500181)

Field Value
Component MSAL.NET — Confidential Client, mTLS Proof-of-Possession (bearer-over-mTLS path)
Severity / Priority 2 / 1 (transient, customer-facing auth failures in S2S)
Status Fix in review — PR #6116
Trace ID ba5e4558-0d10-4c1f-9bf0-118f90957100
Correlation ID ad8a9a7a-962c-48ce-bb64-812091c8edb5

Summary

On the implicit bearer-over-mTLS path (a confidential client configured with WithClientAssertion(...) whose delegate returns a ClientSignedAssertion.TokenBindingCertificate, without .WithMtlsProofOfPossession()), MSAL caches the access token cert-agnostically. After the binding certificate rotates, MSAL serves the previously cached token — still bound (cnf) to the old certificate — while the caller presents the new certificate on the wire, so the resource rejects the call.

Symptom

mTLS PoP signing failed with HTTP 400:
{
  "error": "CertificateValidationFailedTlsCertMismatch",
  "error_description": "AADSTS500181: The TLS certificate provided does not match the certificate in the assertion. Trace ID: ba5e4558-0d10-4c1f-9bf0-118f90957100 Correlation ID: ad8a9a7a-962c-48ce-bb64-812091c8edb5 Timestamp: 2026-07-13 07:45:50Z",
  "error_codes": [500181],
  "timestamp": "2026-07-13 07:45:50Z",
  "trace_id": "ba5e4558-0d10-4c1f-9bf0-118f90957100",
  "correlation_id": "ad8a9a7a-962c-48ce-bb64-812091c8edb5"
}

Repro

  1. Two-leg S2S: Leg 1 = Managed Identity issues a JWT + binding cert; Leg 2 = CCA via WithClientAssertion(_ => MI.GetJwtAndCert()) acquires a token over mTLS as a bearer token (no .WithMtlsProofOfPossession()).
  2. AcquireTokenForClient → call the resource (MSS signing) over mTLS → succeeds.
  3. Rotate the MI binding cert (VM reboot / renewal → new cert + new key).
  4. Within the cached token's lifetime, AcquireTokenForClient again → served from cache (stale, bound to old cert).
  5. Present the new cert to the resource → AADSTS500181.

Root cause

The implicit path leaves the auth scheme as the default BearerAuthenticationOperation, whose KeyId is null. The app-token cache partition key (CacheKeyFactory.GetAppTokenCacheItemKey) and the PoP filter (FilterTokensByPopKeyId) both key off the scheme's KeyId. With KeyId == null the cache is cert-agnostic, so a cert rotation still matches the stale entry. The token is in fact certificate-bound by ESTS, so wire-cert ≠ token cnf → rejection.

The explicit .WithMtlsProofOfPossession() path was unaffected — it already sets KeyId = certificate thumbprint.

Impact

Transient auth failures at the resource for the window between binding-cert rotation and the cached token's expiry (≈ up to the access-token lifetime). Affects S2S callers using the bearer-over-mTLS pattern with a rotating binding cert (e.g., Managed Identity binding certs).

Fix (PR #6116)

Introduce MtlsBoundBearerAuthenticationOperation — identical to bearer on the wire (Authorization: Bearer, no token_type, BindingCertificate not surfaced) but exposing a KeyId derived from the binding certificate — and apply it on the implicit path so the token cache is partitioned per certificate. A rotation now misses the cache and re-mints, mirroring the explicit PoP path.

Validation

  • Full MSAL.NET unit suite green (2236 passed).
  • New regression tests: rotation → cache miss; same cert → cache hit.

Relevant code snippets

Expected behavior

No response

Identity provider

Microsoft Entra ID (Work and School accounts and Personal Microsoft accounts)

Regression

No response

Solution and workarounds

Workaround (until fix ships)

  • Force a fresh token / new CCA instance when the binding certificate changes.

Metadata

Metadata

Assignees

Type

Projects

Status
In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions