Skip to content

Commit 871d32a

Browse files
authored
Merge branch 'main' into gladjohn-see-if-net8-works
2 parents bd1bf8e + e5e4cd2 commit 871d32a

File tree

15 files changed

+195
-46
lines changed

15 files changed

+195
-46
lines changed

docs/msiv1_token_revocation.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# MSAL Support for MSI v1 Token Revocation and Capabilities
2+
3+
---
4+
5+
## Goals
6+
7+
1. App developers and higher level SDKs like Azure SDK rely on the [CAE protocol](https://learn.microsoft.com/en-us/entra/identity-platform/app-resilience-continuous-access-evaluation?tabs=dotnet) for token revocation scenarios (`WithClaims`, `WithClientCapabilities`).
8+
1. RPs are enabled to perform token revocation.
9+
1. A telemetry signal for eSTS exists to differentiate which apps are CAE enlightened.
10+
11+
## Flow diagram - revocation event
12+
13+
```mermaid
14+
sequenceDiagram
15+
participant Resource
16+
actor Client/Caller (CX)
17+
participant MSAL (Leaf)
18+
participant MITS (Proxy)
19+
participant SFRP (RP)
20+
participant eSTS
21+
22+
rect rgb(173, 216, 230)
23+
CX->>Resource: 1. Call resource with "bad" token T
24+
Resource->>CX: 2. HTTP 401 + claims C
25+
CX->>CX: 3. Parse response, extract claims C
26+
CX->>MSAL: 4. MSI.AcquireToken(...).WithClaims(C).WithClientCapabilities("cp1")
27+
end
28+
29+
rect rgb(215, 234, 132)
30+
MSAL->>MSAL: 5. Looks up old token T in local cache
31+
MSAL->>MITS: 6. MITS_endpoint?xms_cc=cp1&token_sha256_to_refresh=SHA256(T)
32+
MITS->>SFRP: 7. (Forward request w/ cc=cp1, hash=SHA256(T))
33+
SFRP->>SFRP: 8. Another MSAL call AcquireTokenForClient(...).WithClientCapabilities(cp1)
34+
SFRP->>eSTS: 9. eSTS issues a new token
35+
end
36+
```
37+
38+
Steps 1-4 fall to the Client (i.e. application using MSI directly or higher level SDK like Azure KeyVault). This is the **standard CAE flow**.
39+
Steps 5-9 are new and show how the RP propagates the revocation signal.
40+
41+
### Explanation:
42+
1. The client (CX) calls some **Resource** with token **T**.
43+
2. The resource detects **T** is bad (revoked) and returns **401** + **claims C**.
44+
3. CX parses **C** and calls **MSAL** with `.WithClaims(C).WithClientCapabilities(cp1)`.
45+
4. MSAL sees the local cached token is "bad" → triggers a refresh flow.
46+
5. MSAL calls **MITS** with `xms_cc=cp1&token_sha256_to_refresh=SHA256(T)`.
47+
6. **MITS** is basically a proxy, forwarding the query to **SFRP**.
48+
7. **SFRP** uses MSAL again to get a **new** token from eSTS.
49+
50+
51+
> [!NOTE]
52+
> ClientCapabilities is an array of capabilities. In case the app developer sends multiple capabilities, these will be sent to the RP as `MITS_endpoint?xms_cc=cp1,cp2,cp3`. The RP MUST pass "cp1" (i.e. the CAE capabilitiy) if it is included.
53+
54+
> [!NOTE]
55+
> Parameter / APIs names are not final.
56+
57+
58+
## Flow diagram - non-revocation event
59+
60+
The client "enlightment" status is still propagated via the client capability "cp1".
61+
62+
```mermaid
63+
sequenceDiagram
64+
actor CX
65+
participant MSAL
66+
participant MITS
67+
participant SFRP
68+
participant eSTS
69+
70+
rect rgb(173, 216, 230)
71+
CX->>MSAL: 1. MSI.AcquireToken <br/> WithClientCapabilities("cp1")
72+
MSAL->>MSAL: 2. Find and return token T in cache. <br/>If not found, goto next step.
73+
end
74+
rect rgb(215, 234, 132)
75+
MSAL->>MITS: 3. Call MITS_endpoint?xms_cc=cp1
76+
MITS->>SFRP: 4. Forward request to SFRP
77+
alt Cache Hit
78+
SFRP->>MSAL: 5a. Return cached token
79+
else Cache Miss
80+
SFRP->>eSTS: 5b. Call CCA.AcquireTokenForClient SN/I cert <br/> WithClientCapabilities(cp1)
81+
eSTS->>SFRP: 6. Return new token
82+
SFRP->>MSAL: 7. Return token to MSAL
83+
end
84+
end
85+
```
86+
87+
### New MSAL API - WithAccessTokenToRefresh()
88+
89+
To support the RP, MSAL will add a new API for `ConfidentialClientApplication.AcquireTokenForClient` - `.WithAccessTokenToRefresh(string tokenHash)`. This may be extended to other flows too in the future.
90+
91+
This API will be in a namespace that indicates it is supposed to be used by RPs - `Microsoft.Identity.Client.RP`.
92+
93+
#### Behavior
94+
95+
- MSAL will look in the cache first, for a non-expired token. If it exists:
96+
- If it matches the "Bad" token SHA256 thumbprint, then MSAL will log this event, ignore the token, and get another token from the STS
97+
- If it doesn't match, it means that a new token was already updated. Return it.
98+
- If it doesn't exist, call eSTS
99+
100+
## `xms_cc` as a List Value (URL Encoding)
101+
102+
### **Multiple Capabilities**
103+
The `xms_cc` parameter can hold **multiple** client capabilities, formatted as:
104+
`xms_cc=cp1,cp2,cp3`
105+
106+
#### **Processing on SFRP:**
107+
1. **On the calling side** (MSAL → MITS → SFRP), always **URL-encode** `xms_cc`, because commas (`,`) must be encoded in queries.
108+
2. **SFRP** must **URL-decode** and **split** on commas:
109+
```csharp
110+
// Example: “cp1,cp2,cp3”
111+
string raw = HttpUtility.UrlDecode(request.Query["xms_cc"]);
112+
string[] caps = raw.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
113+
var clientCapabilities = caps.Select(c => c.Trim());
114+
```
115+
3. **MITS** typically just passes `xms_cc` along to SFRP if it’s acting as a simple proxy.
116+
117+
> [!NOTE]
118+
> RPs or MITS should not bypass cache if a bad token is not passed by MSAL.
119+
120+
#### Motivation
121+
122+
The *internal protocol* between the client and the RP (i.e. calling the MITS endpoint in case of Service Fabric), is a simplified version of CAE. This is because CAE is claims driven and involves JSON operations such as JSON doc merges. The RP doesn't need the actual claims to perform revocation, it just needs a signal to bypass the cache. As such, it was decided to not use the full claims value internally.
123+
124+
## End to End testing
125+
126+
Given the complexity of the scenario, it may not be easy to automate this. Here is the [guideline](https://microsoft.sharepoint.com/:w:/t/AzureMSI/ESBeuafJLZdNlSxkBKvjcswBD4FGVz0o6YJcf4mfDRSH-Q?e=2hJRUt).
127+
128+
## Reference
129+
130+
[Token Revocation docs](https://microsoft.sharepoint.com/:w:/t/AzureMSI/ETSZ_FUzbcxMrcupnuPC8r4BV0dFQrONe1NdjATd3IceLA?e=n72v65)

src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ internal override Task<AuthenticationResult> ExecuteInternalAsync(CancellationTo
165165
}
166166

167167
/// <inheritdoc/>
168+
/// <seealso cref="ConfidentialClientApplicationBuilder.Validate"/> for a comment inside this function for AzureRegion.
168169
protected override void Validate()
169170
{
170171
if (CommonParameters.MtlsCertificate != null)
@@ -187,8 +188,10 @@ protected override void Validate()
187188
}
188189

189190
// Check for Azure region only if the authority is AAD
191+
// AzureRegion is by default set to null or set to null when the application is created
192+
// with region set to DisableForceRegion (see ConfidentialClientApplicationBuilder.Validate)
190193
if (ServiceBundle.Config.Authority.AuthorityInfo.AuthorityType == AuthorityType.Aad &&
191-
string.IsNullOrEmpty(ServiceBundle.Config.AzureRegion))
194+
ServiceBundle.Config.AzureRegion == null)
192195
{
193196
throw new MsalClientException(
194197
MsalError.MtlsPopWithoutRegion,

src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,3 +1027,8 @@ const Microsoft.Identity.Client.MsalError.MtlsPopWithoutRegion = "mtls_pop_witho
10271027
const Microsoft.Identity.Client.MsalError.RegionRequiredForMtlsPop = "region_required_for_mtls_pop" -> string
10281028
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.MachineLearning = 7 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
10291029
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithFmiPath(string pathSuffix) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
1030+
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
1031+
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
1032+
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
1033+
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
1034+
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
2-
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
3-
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
4-
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
5-
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder

src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,3 +1027,8 @@ const Microsoft.Identity.Client.MsalError.MtlsPopWithoutRegion = "mtls_pop_witho
10271027
const Microsoft.Identity.Client.MsalError.RegionRequiredForMtlsPop = "region_required_for_mtls_pop" -> string
10281028
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.MachineLearning = 7 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
10291029
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithFmiPath(string pathSuffix) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
1030+
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
1031+
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
1032+
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
1033+
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
1034+
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
2-
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
3-
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
4-
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
5-
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder

src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,3 +993,8 @@ const Microsoft.Identity.Client.MsalError.MtlsPopWithoutRegion = "mtls_pop_witho
993993
const Microsoft.Identity.Client.MsalError.RegionRequiredForMtlsPop = "region_required_for_mtls_pop" -> string
994994
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.MachineLearning = 7 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
995995
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithFmiPath(string pathSuffix) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
996+
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
997+
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
998+
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
999+
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
1000+
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
2-
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
3-
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
4-
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
5-
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder

src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,3 +995,8 @@ const Microsoft.Identity.Client.MsalError.MtlsPopWithoutRegion = "mtls_pop_witho
995995
const Microsoft.Identity.Client.MsalError.RegionRequiredForMtlsPop = "region_required_for_mtls_pop" -> string
996996
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.MachineLearning = 7 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
997997
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithFmiPath(string pathSuffix) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
998+
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
999+
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
1000+
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
1001+
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
1002+
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
2-
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
3-
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
4-
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
5-
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder

0 commit comments

Comments
 (0)