Skip to content

Commit 4de2a41

Browse files
committed
[Identity] Bypass cache when claims provided in Managed Identity
Ensure the claims are propagated to the MSAL ManagedIdentityClient or, for those credentials not using MSAL, the cache is bypassed when claims are provided so that a new token request is made. Signed-off-by: Paul Van Eck <paulvaneck@microsoft.com>
1 parent a65d727 commit 4de2a41

12 files changed

Lines changed: 140 additions & 12 deletions

File tree

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
### Bugs Fixed
1414

15-
- Fixed an issue with certain credentials not bypassing the token cache when claims are provided in `get_token` or `get_token_info` calls. ([#44552](https://github.com/Azure/azure-sdk-for-python/pull/44552))
15+
- Fixed an issue with certain credentials not bypassing the token cache when claims are provided in `get_token` or `get_token_info` calls. ([#44552](https://github.com/Azure/azure-sdk-for-python/pull/44552)) ([#44815](https://github.com/Azure/azure-sdk-for-python/pull/44815))
1616
- Fixed an issue where an unhelpful TypeError was raised during Entra ID token requests that returned empty responses. Now, a ClientAuthenticationError is raised with the full response for better troubleshooting. ([#44258](https://github.com/Azure/azure-sdk-for-python/pull/44258))
1717

1818
### Other Changes

sdk/identity/azure-identity/azure/identity/_credentials/imds.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def _request_token(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo:
124124
raise CredentialUnavailableError(error_message) from ex
125125

126126
try:
127-
token_info = super()._request_token(*scopes)
127+
token_info = super()._request_token(*scopes, **kwargs)
128128
except CredentialUnavailableError:
129129
# Response is not json, skip the IMDS credential
130130
raise

sdk/identity/azure-identity/azure/identity/_credentials/managed_identity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ def get_token(
174174
:param str scopes: desired scope for the access token. This credential allows only one scope per request.
175175
For more information about scopes, see
176176
https://learn.microsoft.com/entra/identity-platform/scopes-oidc.
177-
178-
:keyword str claims: not used by this credential; any value provided will be ignored.
177+
:keyword str claims: additional claims required in the token, such as those returned in a resource provider's
178+
claims challenge following an authorization failure.
179179
:keyword str tenant_id: not used by this credential; any value provided will be ignored.
180180
181181
:return: An access token with the desired scopes.

sdk/identity/azure-identity/azure/identity/_internal/managed_identity_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def get_token_info(self, *scopes: str, options: Optional[TokenRequestOptions] =
5555
def _acquire_token_silently(self, *scopes: str, **kwargs: Any) -> Optional[AccessTokenInfo]:
5656
# casting because mypy can't determine that these methods are called
5757
# only by get_token, which raises when self._client is None
58-
return cast(ManagedIdentityClient, self._client).get_cached_token(*scopes)
58+
return cast(ManagedIdentityClient, self._client).get_cached_token(*scopes, **kwargs)
5959

6060
def _request_token(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo:
6161
return cast(ManagedIdentityClient, self._client).request_token(*scopes, **kwargs)

sdk/identity/azure-identity/azure/identity/_internal/managed_identity_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ def _process_response(self, response: PipelineResponse, request_time: int) -> Ac
9090

9191
return token
9292

93-
def get_cached_token(self, *scopes: str) -> Optional[AccessTokenInfo]:
93+
def get_cached_token(self, *scopes: str, **kwargs: Any) -> Optional[AccessTokenInfo]:
94+
# Do not return a cached token if claims are provided.
95+
if kwargs.get("claims") is not None:
96+
return None
97+
9498
resource = _scopes_to_resource(*scopes)
9599
now = time.time()
96100
for token in self._cache.search(TokenCache.CredentialType.ACCESS_TOKEN, target=[resource]):

sdk/identity/azure-identity/azure/identity/_internal/msal_managed_identity_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ def get_unavailable_message(self, desc: str = "") -> str:
4545
def close(self) -> None:
4646
self.__exit__()
4747

48-
def _request_token(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo: # pylint:disable=unused-argument
48+
def _request_token(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo:
4949
if not scopes:
5050
raise ValueError('"get_token" requires at least one scope')
5151
resource = _scopes_to_resource(*scopes)
52-
result = self._msal_client.acquire_token_for_client(resource=resource)
52+
result = self._msal_client.acquire_token_for_client(resource=resource, claims_challenge=kwargs.get("claims"))
5353
now = int(time.time())
5454
if result and "access_token" in result and "expires_in" in result:
5555
refresh_on = int(result["refresh_on"]) if "refresh_on" in result else None

sdk/identity/azure-identity/azure/identity/aio/_credentials/imds.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ async def close(self) -> None:
6565
await self._client.close()
6666

6767
async def _acquire_token_silently(self, *scopes: str, **kwargs: Any) -> Optional[AccessTokenInfo]:
68-
return self._client.get_cached_token(*scopes)
68+
return self._client.get_cached_token(*scopes, **kwargs)
6969

7070
async def _request_token(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo:
7171

sdk/identity/azure-identity/azure/identity/aio/_credentials/managed_identity.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ async def get_token(
143143
:param str scopes: desired scope for the access token. This credential allows only one scope per request.
144144
For more information about scopes, see
145145
https://learn.microsoft.com/entra/identity-platform/scopes-oidc.
146-
:keyword str claims: not used by this credential; any value provided will be ignored.
146+
:keyword str claims: additional claims required in the token, such as those returned in a resource provider's
147+
claims challenge following an authorization failure.
147148
:keyword str tenant_id: not used by this credential; any value provided will be ignored.
148149
149150
:return: An access token with the desired scopes.

sdk/identity/azure-identity/azure/identity/aio/_internal/managed_identity_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async def get_token_info(self, *scopes: str, options: Optional[TokenRequestOptio
6262
async def _acquire_token_silently(self, *scopes: str, **kwargs) -> Optional[AccessTokenInfo]:
6363
# casting because mypy can't determine that these methods are called
6464
# only by get_token, which raises when self._client is None
65-
return cast(AsyncManagedIdentityClient, self._client).get_cached_token(*scopes)
65+
return cast(AsyncManagedIdentityClient, self._client).get_cached_token(*scopes, **kwargs)
6666

6767
async def _request_token(self, *scopes: str, **kwargs) -> AccessTokenInfo:
6868
return await cast(AsyncManagedIdentityClient, self._client).request_token(*scopes, **kwargs)

sdk/identity/azure-identity/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ classifiers = [
2626
dependencies = [
2727
"azure-core>=1.31.0",
2828
"cryptography>=2.5",
29-
"msal>=1.30.0",
29+
"msal>=1.31.0",
3030
"msal-extensions>=1.2.0",
3131
"typing-extensions>=4.0.0",
3232
]

0 commit comments

Comments
 (0)