-
Notifications
You must be signed in to change notification settings - Fork 88
Adding MsalTokenCredential implementation of azure.core.credentials.AsyncTokenProvider
#565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Rodrigo Brandão (rodrigobr-msft)
merged 16 commits into
main
from
users/robrandao/msal-token-cred
Aug 27, 2026
+359
−6
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
b008d7e
MsalTokenCredential and AsyncMsalTokenCredential definitions
rodrigobr-msft 9a29248
Adding internal _get_access_token method
rodrigobr-msft 45708db
Improved handling of resource from scope
rodrigobr-msft 7aa02dc
Improvements
rodrigobr-msft 1d98e10
Fixing _get_resource
rodrigobr-msft d4fbcfc
Potential fix for pull request finding
rodrigobr-msft e33c116
Potential fix for pull request finding
rodrigobr-msft 26347f4
Update
rodrigobr-msft a0224c7
Merge branch 'users/robrandao/msal-token-cred' of https://github.com/…
rodrigobr-msft 79e30e4
Removing section from README
rodrigobr-msft 24c5a9a
Lazy construction of MsalAuth instance
rodrigobr-msft 445bd32
Fixing changelog and tests
rodrigobr-msft 186b03c
Improving integration test
rodrigobr-msft 1a69c71
Adding direct dependency on azure.core in microsoft-agents-authentica…
rodrigobr-msft 0252d1e
Merge branch 'main' into users/robrandao/msal-token-cred
rodrigobr-msft d4ef5e2
Potential fix for pull request finding
rodrigobr-msft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. | ||
|
|
||
| import asyncio | ||
| import os | ||
| import time | ||
|
|
||
| import jwt | ||
| import pytest | ||
| from azure.core.credentials import AccessToken | ||
| from dotenv import dotenv_values | ||
| from jwt import PyJWKClient | ||
|
|
||
| from microsoft_agents.authentication.msal import MsalTokenCredential | ||
| from microsoft_agents.hosting.core import AgentAuthConfiguration | ||
|
|
||
| from tests.utils.config import REAL_SERVICE_CONNECTION_ENV_VARS | ||
| from tests.utils.pytest import skip_if_no_var | ||
|
|
||
| _CLIENT_ID_ENV_VAR = "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID" | ||
| _CLIENT_SECRET_ENV_VAR = "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET" | ||
| _TENANT_ID_ENV_VAR = "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID" | ||
| _BOT_FRAMEWORK_RESOURCE = "https://api.botframework.com" | ||
| _BOT_FRAMEWORK_SCOPE = f"{_BOT_FRAMEWORK_RESOURCE}/.default" | ||
| _ENVIRONMENT = {**dotenv_values(".env"), **os.environ} | ||
|
|
||
| pytestmark = skip_if_no_var( | ||
| *REAL_SERVICE_CONNECTION_ENV_VARS, | ||
| environ=_ENVIRONMENT, | ||
| ) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def auth_config() -> AgentAuthConfiguration: | ||
| return AgentAuthConfiguration( | ||
| client_id=_ENVIRONMENT[_CLIENT_ID_ENV_VAR], | ||
| client_secret=_ENVIRONMENT[_CLIENT_SECRET_ENV_VAR], | ||
| tenant_id=_ENVIRONMENT[_TENANT_ID_ENV_VAR], | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_msal_token_credential_acquires_valid_token( | ||
| auth_config: AgentAuthConfiguration, | ||
| ): | ||
| credential = MsalTokenCredential(auth_config) | ||
|
|
||
| token = await credential.get_token(_BOT_FRAMEWORK_SCOPE) | ||
|
|
||
| assert isinstance(token, AccessToken) | ||
| assert token.token | ||
| assert token.expires_on > time.time() | ||
|
|
||
| unverified_claims = jwt.decode( | ||
| token.token, | ||
| options={"verify_signature": False}, | ||
| ) | ||
| token_version = unverified_claims.get("ver") | ||
| assert token_version in ("1.0", "2.0") | ||
|
|
||
| tenant_id = auth_config.TENANT_ID | ||
| issuer = ( | ||
| f"https://login.microsoftonline.com/{tenant_id}/v2.0" | ||
| if token_version == "2.0" | ||
| else f"https://sts.windows.net/{tenant_id}/" | ||
| ) | ||
| jwks_client = PyJWKClient( | ||
| f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys" | ||
| ) | ||
| signing_key = await asyncio.to_thread( | ||
| jwks_client.get_signing_key_from_jwt, | ||
| token.token, | ||
| ) | ||
|
|
||
| claims = jwt.decode( | ||
| token.token, | ||
| signing_key.key, | ||
| algorithms=["RS256"], | ||
| audience=_BOT_FRAMEWORK_RESOURCE, | ||
| issuer=issuer, | ||
| ) | ||
|
|
||
| assert claims["tid"] == tenant_id | ||
| assert abs(claims["exp"] - token.expires_on) <= 5 | ||
| client_id_claim = "azp" if token_version == "2.0" else "appid" | ||
| assert claims[client_id_claim] == auth_config.CLIENT_ID |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
...ies/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,12 @@ | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. | ||
|
|
||
| from .msal_auth import MsalAuth | ||
| from .msal_connection_manager import MsalConnectionManager | ||
| from .msal_token_credential import MsalTokenCredential | ||
|
|
||
| __all__ = [ | ||
| "MsalAuth", | ||
| "MsalConnectionManager", | ||
| "MsalTokenCredential", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
...-agents-authentication-msal/microsoft_agents/authentication/msal/msal_token_credential.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. | ||
|
|
||
| import logging | ||
|
|
||
| from azure.core.credentials import AccessToken | ||
| from azure.core.credentials_async import AsyncTokenCredential | ||
|
|
||
|
rodrigobr-msft marked this conversation as resolved.
|
||
| from microsoft_agents.hosting.core import AgentAuthConfiguration | ||
|
|
||
| from .msal_auth import MsalAuth | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def _get_resource(scope: str) -> str: | ||
| """Extracts the resource by removing a trailing '/.default' from the scope. | ||
|
|
||
| :param scope: The scope string. | ||
| :return: The extracted resource string. | ||
| :rtype: str | ||
| """ | ||
| return scope.removesuffix("/.default") | ||
|
|
||
|
|
||
| class MsalTokenCredential(AsyncTokenCredential): | ||
| """Provides an asynchronous Azure Core token credential using MSAL.""" | ||
|
|
||
| def __init__(self, config: AgentAuthConfiguration): | ||
| """Initializes the MsalTokenCredential with the given configuration. | ||
|
|
||
| :param config: The agent authentication configuration. | ||
| :type config: :class:`microsoft_agents.hosting.core.AgentAuthConfiguration` | ||
| """ | ||
| self._config = config | ||
| self._provider: MsalAuth | None = None | ||
|
|
||
| async def get_token(self, *scopes: str, **kwargs) -> AccessToken: | ||
| """Acquire an access token for the specified scopes. | ||
|
|
||
| :param scopes: The scopes for which the access token is requested. | ||
| :param kwargs: Additional keyword arguments. | ||
|
|
||
| :return: The acquired access token. | ||
| :rtype: AccessToken | ||
| """ | ||
|
|
||
| logger.debug("get_token scope=%s", scopes) | ||
|
|
||
| if not scopes: | ||
| raise ValueError("At least one scope must be provided.") | ||
|
|
||
| if not self._provider: | ||
| self._provider = MsalAuth(self._config) | ||
|
|
||
| resource = _get_resource(scopes[0]) | ||
|
|
||
| return await self._provider._get_access_token(resource, list(scopes)) | ||
|
rodrigobr-msft marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.