Authorization AsyncTokenCredential-returning variants - #568
Authorization AsyncTokenCredential-returning variants#568Rodrigo Brandão (rodrigobr-msft) wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the Microsoft 365 Agents SDK for Python authentication/authorization surface to interoperate with Azure SDKs by introducing azure.core.credentials_async.AsyncTokenCredential-returning APIs and wrappers, while modernizing type annotations in the OAuth Authorization flow.
Changes:
- Added Azure
AsyncTokenCredentialwrappers for MSAL and Sidecar auth providers (MsalTokenCredential,SidecarTokenCredential) and provider entrypoints (get_token_credential). - Introduced
_CallableTokenCredential+ helpers to bridge internalTokenResponseto AzureAccessToken. - Extended
Authorizationwith credential-returning convenience methods and updated type hints to| None.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| libraries/microsoft-agents-hosting-core/setup.py | Adds Azure dependency for hosting-core (currently uses incorrect distribution name). |
| libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/access_token_provider_base.py | Adds get_token_credential() to the provider protocol (new API contract). |
| libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/_helpers.py | Adds TokenResponse → AccessToken conversion and callable-backed AsyncTokenCredential. |
| libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py | Adds credential-returning methods on OAuth Authorization and updates typing. |
| libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/sdk_token_credential.py | Present in PR metadata, but currently empty in the checkout. |
| libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py | Adds get_token_credential() returning an Azure-compatible credential wrapper. |
| libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_token_credential.py | Allows injecting an MSAL provider instance and wraps MSAL token acquisition as AsyncTokenCredential. |
| libraries/microsoft-agents-authentication-entra-auth-sidecar/microsoft_agents/authentication/entra_auth_sidecar/sidecar_auth.py | Adds get_token_credential() returning an Azure-compatible credential wrapper. |
| libraries/microsoft-agents-authentication-entra-auth-sidecar/microsoft_agents/authentication/entra_auth_sidecar/sidecar_token_credential.py | New AsyncTokenCredential wrapper around Sidecar token acquisition. |
Suppressed comments (3)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py:416
get_token_as_token_credential()passes Azure SDK**kwargsthrough toexchange_token(), butexchange_token()doesn't accept arbitrary kwargs. Azure SDKs commonly passclaims,tenant_id, etc. intoget_token(), so this will raiseTypeErrorwhen used as anAsyncTokenCredential.
async def func(*scopes: str, **kwargs) -> TokenResponse:
return await self.exchange_token(
context, auth_handler_id=auth_handler_id, scopes=list(scopes), **kwargs
)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py:506
- In
exchange_token_as_token_credential(),all_scopes = list(set(scopes or [] + list(new_scopes)))has two problems: (1) whenscopesis a non-empty list it short-circuits theorand ignoresnew_scopes, and (2)set()discards order, which can be surprising when scopes are logged/debugged. This can cause credentials to request the wrong scopes.
async def func(*new_scopes: str, **kwargs) -> TokenResponse:
all_scopes = list(set(scopes or [] + list(new_scopes)))
return await self.exchange_token(
context,
scopes=all_scopes,
auth_handler_id=auth_handler_id,
exchange_connection=exchange_connection,
)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py:411
- Docstring types for
get_token_as_token_credential()are inconsistent with the actual signature (e.g.auth_handler_idcan beNone, and:rtype:points tomicrosoft_agents...authorization.AsyncTokenCredentialwhich isn't a real type; the return type isazure.core.credentials_async.AsyncTokenCredential). This can mislead users relying on generated docs.
:param auth_handler_id: The ID of the auth handler to get the token for.
:type auth_handler_id: str
:return: An AsyncTokenCredential for the specified auth handler or the default handler.
:rtype: :class:`microsoft_agents.hosting.core.app.oauth.authorization.AsyncTokenCredential`
"""
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py:410
- The docstring types don’t match the signature/return type here:
auth_handler_idcan beNone, and the:rtype:should referenceazure.core.credentials_async.AsyncTokenCredential(the current module path doesn’t exist). This can break generated docs and mislead SDK consumers.
:type context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext`
:param auth_handler_id: The ID of the auth handler to get the token for.
:type auth_handler_id: str
:return: An AsyncTokenCredential for the specified auth handler or the default handler.
:rtype: :class:`microsoft_agents.hosting.core.app.oauth.authorization.AsyncTokenCredential`
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py:7
overloadandLiteralare imported but never used in this module, which adds avoidable lint noise and makes the import list harder to scan. Remove the unused names from the typing import.
from typing import Optional, Callable, Awaitable, cast, overload, Literal
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py:416
get_token_as_token_credential()forwardsscopes=list(scopes)toexchange_token(). When the Azure SDK (or a consumer) callscredential.get_token()with no scopes, this passes[]rather thanNone, which preventsexchange_token()from falling back to the handler’s configured default scopes (it only defaults whenscopes is None).
This issue also appears on line 500 of the same file.
async def func(*scopes: str, **_kwargs) -> TokenResponse:
return await self.exchange_token(
context, auth_handler_id=auth_handler_id, scopes=list(scopes)
)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py:504
exchange_token_as_token_credential()always passes a (possibly empty) list of scopes toexchange_token(). If the wrapper is called with no scopes and the caller didn’t provide initialscopes, this results in[]instead ofNone, which prevents the downstream handler from using its configured default scopes.
prev_scopes: list[str] = scopes or []
all_scopes = list(dict.fromkeys([*prev_scopes, *new_scopes]))
return await self.exchange_token(
context,
scopes=all_scopes,
This pull request introduces significant improvements to the authentication and authorization infrastructure, focusing on enhanced interoperability with Azure SDKs and improved type safety. The main changes include the addition of new
AsyncTokenCredentialwrappers for token retrieval, refactoring of method signatures for clarity, and the introduction of utility helpers to bridge internal token types with Azure's credential interfaces.Azure SDK Interoperability and Token Credential Support:
get_token_credentialmethods to bothSidecarAuthandMsalAuthclasses, allowing them to returnAsyncTokenCredentialobjects compatible with Azure SDKs. CorrespondingSidecarTokenCredentialandMsalTokenCredentialclasses were implemented to encapsulate the token retrieval logic. [1] [2] [3] [4]_helpers.pywith the_CallableTokenCredentialclass, which wraps an async callable to provide anAsyncTokenCredentialinterface, and helper functions to convert internalTokenResponseobjects to Azure'sAccessToken.Authorization API Enhancements:
Authorizationclass now providesget_token_as_token_credentialandexchange_token_as_token_credentialmethods, enabling consumers to obtain Azure-compatible token credentials directly from authorization flows. [1] [2]authorization.pyto use| Nonesyntax instead ofOptional, improving code clarity and aligning with modern Python typing conventions. [1] [2] [3] [4] [5] [6] [7] [8]Type Safety and Consistency:
str | Noneinstead ofOptional[str], and similar changes for other types, enhancing type safety and readability. [1] [2] [3] [4] [5] [6] [7] [8]Dependency Imports and Code Organization:
AsyncTokenCredentialand related Azure SDK types in relevant modules to support the new credential features. [1] [2] [3] [4] [5]These updates collectively make the authentication system more robust, extensible, and compatible with Azure's ecosystem, while also improving code maintainability and developer experience.