Azure / Azure/azure-sdk-for-rust
Key Vault challenge-based authentication fails to extract tenant ID from DSTSv2 authority URIs
- Dominant language
- Rust
- Stars
- 884
- Forks
- 365
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 109
Description
# Key Vault challenge-based authentication fails to extract tenant ID from DSTSv2 authority URIs
## Summary
The Key Vault challenge-based authentication policy incorrectly parses the tenant ID from `WWW-Authenticate` challenge responses when the authorization URI uses a DSTSv2 (Distributed Security Token Service v2) authority format. This causes authentication failures when connecting to Key Vault endpoints that return DSTSv2 challenges.
## Background
When a Key Vault client makes an unauthenticated request, the service responds with a `401 Unauthorized` containing a `WWW-Authenticate` header like:
```
Bearer authorization="https://login.microsoftonline.com/{tenantId}", resource="https://vault.azure.net"
```
The client parses the `authorization` URI to extract the tenant ID (typically the first path segment) and uses it to acquire a token.
## The Bug
The current implementation assumes the tenant ID is always the **first path segment** of the authorization URI. This is correct for standard Entra ID (AAD) authorities:
```
https://login.microsoftonline.com/{tenantId}/oauth2/authorize
^^^^^^^^^
Segments[1] = tenantId ✅
```
However, DSTSv2 authority URIs have a **different path layout** where `dstsv2` is the first path segment and the tenant ID is the **second**:
```
https://uswest2-passive-dsts.dsts.core.windows.net/dstsv2/{tenantId}
^^^^^^ ^^^^^^^^^
Seg[1] Seg[2] = tenantId
```
With the current logic, the extracted "tenant ID" is the literal string `dstsv2` instead of the actual GUID, causing token acquisition to fail or target the wrong tenant.
### Example challenge header from a DSTSv2 endpoint
```
Bearer authorization="https://uswest2-passive-dsts.dsts.core.windows.net/dstsv2/de763a21-49f7-4b08-a8e1-52c8fbc103b4", resource="https://foo.bar.core.windows.net"
```
## Expected Behavior
The tenant ID should be correctly extracted as `de763a21-49f7-4b08-a8e1-52c8fbc103b4`, regardless of whether the authority URI is a standard Entra ID endpoint or a DSTSv2 endpoint.
## Fix Description
When parsing the authorization URI to extract the tenant ID:
1. Extract the first path segment as usual.
2. If the first path segment equals `dstsv2` (case-insensitive) **and** there is a subsequent path segment, use that subsequent segment as the tenant ID instead.
### Pseudocode
```
tenantId = authorizationUri.pathSegments[1] // e.g. "dstsv2" or a GUID
if tenantId.equalsIgnoreCase("dstsv2") AND pathSegments.length > 2:
tenantId = authorizationUri.pathSegments[2]
```
## Affected Area
This bug is in the **Key Vault challenge-based authentication policy** — the component that handles the `401 → parse challenge → acquire token → retry` flow. In each language SDK, this is typically:
| Language | Approximate Location |
|----------|---------------------|
| .NET | `ChallengeBasedAuthenticationPolicy.ChallengeParameters` constructor |
| Python | `ChallengeAuthPolicy` / `HttpChallenge` class |
| Java | `KeyVaultCredentialPolicy` |
| JavaScript/TypeScript | `challengeBasedAuthenticationPolicy` / challenge parsing utilities |
| Go | `challengePolicy` / challenge parsing in `azkeys`, `azsecrets`, `azcertificates` |
## Reference Fix
The .NET fix is in [Azure/azure-sdk-for-net#56416](https://github.com/Azure/azure-sdk-for-net/pull/56416).
Key change in `ChallengeBasedAuthenticationPolicy.cs`:
```csharp
TenantId = authorizationUri.Segments[1].Trim('/');
if (TenantId.Equals("dstsv2", StringComparison.OrdinalIgnoreCase) && authorizationUri.Segments.Length > 2)
{
TenantId = authorizationUri.Segments[2].Trim('/');
}
```
## Suggested Test Case
Simulate a Key Vault challenge response with a DSTSv2 authorization URI and verify the tenant ID is correctly extracted:
1. Mock a `401` response with header:
```
WWW-Authenticate: Bearer authorization="https://uswest2-passive-dsts.dsts.core.windows.net/dstsv2/{expectedTenantGuid}", resource="https://foo.bar.core.windows.net"
```
2. Mock a `200` response for the subsequent authenticated request.
3. Capture the tenant ID passed to the credential's `getToken` call.
4. Assert the captured tenant ID equals the expected GUID, **not** the literal string `"dstsv2"`.
## Labels
`KeyVault`, `bug`, `client`
Contributor guide
Research direction
Start with the Rust challengePolicy and challenge parsing in azkeys, azsecrets, and azcertificates, then trace the 401 challenge through token acquisition and retry. Add the suggested mocked DSTSv2 challenge test and verify that the credential's getToken call receives the expected tenant GUID rather than "dstsv2".
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- authentication
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100