Azure / Azure/azure-sdk-for-python

Key Vault challenge-based authentication fails to extract tenant ID from DSTSv2 authority URIs

Open
#45,326 1 comment 0 reactions 0 assignees View on GitHub
Client KeyVault Service Attention
Dominant language
Python
Stars
5.6k
Forks
3.4k
Avg merge
1d 21h
Merged PRs (30d)
193

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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.