diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py index ef5c3ceec08d..f07b2c0b6e85 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -61,18 +61,24 @@ def _build_auth_record(response): """Build an AuthenticationRecord from the result of an MSAL ClientApplication token request""" try: - client_info = json.loads(_decode_client_info(response["client_info"])) id_token = response["id_token_claims"] + if "client_info" in response: + client_info = json.loads(_decode_client_info(response["client_info"])) + home_account_id = "{uid}.{utid}".format(**client_info) + else: + # MSAL uses the subject claim as home_account_id when the STS doesn't provide client_info + home_account_id = id_token["sub"] + return AuthenticationRecord( authority=urlparse(id_token["iss"]).netloc, # "iss" is the URL of the issuing tenant client_id=id_token["aud"], - home_account_id="{uid}.{utid}".format(**client_info), + home_account_id=home_account_id, tenant_id=id_token["tid"], # tenant which issued the token, not necessarily user's home tenant username=id_token["preferred_username"], ) except (KeyError, ValueError): - # surprising: msal.ClientApplication always requests client_info and an id token, whose shapes shouldn't change + # surprising: msal.ClientApplication always requests an id token, whose shape shouldn't change return None diff --git a/sdk/identity/azure-identity/tests/test_interactive_credential.py b/sdk/identity/azure-identity/tests/test_interactive_credential.py index 631844567f09..0f3efd57d32e 100644 --- a/sdk/identity/azure-identity/tests/test_interactive_credential.py +++ b/sdk/identity/azure-identity/tests/test_interactive_credential.py @@ -18,6 +18,8 @@ except ImportError: # python < 3.3 from mock import Mock, patch # type: ignore +from helpers import build_aad_response + class MockCredential(InteractiveCredential): """Test class to drive InteractiveCredential. @@ -266,3 +268,54 @@ def _request_token(self, *_, **__): TestCredential(enable_persistent_cache=True, allow_unencrypted_cache=True) assert mock_extensions.PersistedTokenCache.called_with(mock_extensions.FilePersistence) + + +def test_home_account_id_client_info(): + """when MSAL returns client_info, the credential should decode it to get the home_account_id""" + + object_id = "object-id" + home_tenant = "home-tenant-id" + msal_response = build_aad_response(uid=object_id, utid=home_tenant, access_token="***", refresh_token="**") + msal_response["id_token_claims"] = { + "aud": "client-id", + "iss": "https://localhost", + "object_id": object_id, + "tid": home_tenant, + "preferred_username": "me", + "sub": "subject", + } + + class TestCredential(InteractiveCredential): + def __init__(self, **kwargs): + super(TestCredential, self).__init__(client_id="...", **kwargs) + + def _request_token(self, *_, **__): + return msal_response + + record = TestCredential().authenticate() + assert record.home_account_id == "{}.{}".format(object_id, home_tenant) + + +def test_home_account_id_no_client_info(): + """the credential should use the subject claim as home_account_id when MSAL doesn't provide client_info""" + + subject = "subject" + msal_response = build_aad_response(access_token="***", refresh_token="**") + msal_response["id_token_claims"] = { + "aud": "client-id", + "iss": "https://localhost", + "object_id": "some-guid", + "tid": "some-tenant", + "preferred_username": "me", + "sub": subject, + } + + class TestCredential(InteractiveCredential): + def __init__(self, **kwargs): + super(TestCredential, self).__init__(client_id="...", **kwargs) + + def _request_token(self, *_, **__): + return msal_response + + record = TestCredential().authenticate() + assert record.home_account_id == subject