Skip to content

Conversation

@chlowell
Copy link
Owner

@chlowell chlowell commented Mar 12, 2020

usage

az login

from azure.identity import AuthenticationRequiredError, InteractiveBrowserCredential

# 'authenticate' returns an authenticated credential and
# a profile representing the authenticated identity
credential, profile = InteractiveBrowserCredential.authenticate(
    # this arg configures the returned credential to raise AuthenticationRequiredError
    # when silent auth fails rather than start an interactive login
    silent_auth_only=True
)

# serialize the profile to JSON, including all keyword arguments
profile_json = profile.serialize(extra='args', serialized='also')
with open(PROFILE_PATH, 'w') as f:
    f.write(profile_json)

# credential is ready to use, will authenticate silently only
try:
    token = credential.get_token('https://management.azure.com/.default')
except AuthenticationRequiredError:
    # silent authentication failed
    ...

az command ...

import sys
from azure.identity import (
    AuthenticationRequiredError,
    AuthProfile,
    DeviceCodeCredential
)

# try to load a profile from a known location
try:
    with open(PROFILE_PATH) as f:
        # 'deserialize' ignores additional keyword arguments passed to 'serialize'
        profile = AuthProfile.deserialize(f.read())
except IOError:
    # no profile -> user must log in
    print("run 'az login'")
    sys.exit(1)

# given a profile and silent_auth_only=True, a user credential (DeviceCodeCredential,
# InteractiveBrowserCredential, UsernamePasswordCredential) will authenticate silently
# only, using a cached account matching that profile
credential = DeviceCodeCredential(client_id=CLIENT_ID, profile=profile, silent_auth_only=True)

try:
    # pass the credential to some client, which eventually requests a token
    token = credential.get_token('https://management.azure.com/.default')
except AuthenticationRequiredError:
    # silent authentication failed -> user must log in
    print("run 'az login'")
    sys.exit(1)

@chlowell chlowell force-pushed the profile-prototype branch from 0ac48c7 to f004cfa Compare March 17, 2020 17:45

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and account.get("realm") == self._profile.tenant_id [](start = 11, length = 52)

this check is incorrect based on my test, the value of account.get('realm') is 'organizations'. I think we do not need this check since home_account_id include the tenant id

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I specify tenant_id like
credential, profile = InteractiveBrowserCredential.authenticate(
# silent_auth_only=True
scope = 'https://management.azure.com/.default',
tenant_id = tenant
)
The value of realm is the tenant_id. But in this scenario, the cached account still not match the profile. The home_account_id in the profile is correct, but the home_account_id in the MSAL cache is my organization tenants's home_account_id


In reply to: 396249087 [](ancestors = 396249087)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct we needn't check the realm, thank you for pointing that out.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return AccessToken(token["access_token"], now + int(token["expires_in"])) [](start = 20, length = 73)

can you add token_type in AccessToken which is needed for client to use, by default token_type is "Bearer"

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the documentation I've read (for example) Azure AD provides only bearer tokens in the flows we use. What other types do you expect, and how does the client use them?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we add this type in the authorization head of http request. AAD may add new type in the future so that I would prefer not hard-code in client code. Currently CLI get the value from ADAL token information.


In reply to: 398762506 [](ancestors = 398762506)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to extend AccessToken without a definite use case. If AAD adds a new token type in the future, using it in azure-identity will require design and implementation work I don't want to try to preempt today.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) [](start = 8, length = 1)

sorry that I was wrong, the home_account_id is the home_tenent_id, we still need and account.get("realm") == self._profile.tenant_id

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practical terms I think home_account_id alone is sufficient because it's the only detail of the account MSAL uses to find a token: finding an access token, finding a refresh token. The environment and realm (tenant) are set when the credential's msal.PublicClientApplication instance is created. So it looks like this filter could be much simpler: take the first account with a matching home_account_id.

I want to be sure my understanding is correct though. Does the above make sense to you?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, since you will loop the accounts (may have multiple tenant accounts per user account) to try to acquire access token, I think this behavior is correct.


In reply to: 398946277 [](ancestors = 398946277)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can let client_id default to None? Thus we can automatically use AZURE_CLI_CLIENT_ID. Otherwise,

            credential, profile = InteractiveBrowserCredential.authenticate(
                silent_auth_only=True,
                scope='https://management.azure.com/.default'
            )

gives

TypeError: authenticate() missing 1 required positional argument: 'client_id'

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API is intended for applications beyond the CLI, which we don't want unintentionally authenticating users to the CLI. It's true InteractiveBrowserCredential.get_token does that today, but that was designed for a different scenario and in a future release its implicit default client id will change.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Thanks for the clarification.

Copy link

@jiasli jiasli Apr 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I know the difference between SharedTokenCache and DeviceCodeCredential, InteractiveBrowserCredential, UsernamePasswordCredential with profile provided?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SharedTokenCacheCredential handles the cache directly, essentially reimplementing parts of msal to support a narrow scenario. The other credentials you list defer caching entirely to msal. I intend to remove all the changes to SharedTokenCacheCredential from this PR. It will continue to support its particular scenario and won't be part of the new user authentication API.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am actually thinking if we can provide a helper for cross-tenant get_token or simple support credential.get_token(tenant), since the credential already has everything we need, including _profile, _username, environment, etc.

credential, msal_profile = InteractiveBrowserCredential.authenticate(
    client_id=_CLIENT_ID,
    silent_auth_only=True,
    scope='https://management.azure.com/.default'
)

    specific_tenant_credential = SharedTokenCacheCredential(username=credential._profile.username,
                                                            authority=credential._profile.environment,
                                                            tenant_id=tenant_id)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct the credential has enough information to authenticate in arbitrary tenants. However, credential and msal application instances are designed to authenticate in a single tenant. Revising that design for credentials requires careful thought and a (likely clumsy) workaround for msal.

I expect we'll keep the current design of a credential instance representing a single identity in a single tenant. For now, here's a way to create a credential instance per tenant:

credential, profile = InteractiveBrowserCredential.authenticate(client_id=_CLIENT_ID)

for other_tenant in tenants:
    new_profile = AuthProfile(
        environment=profile.environment,
        home_account_id=profile.home_account_id,
        tenant_id=other_tenant,
        username=profile.username
    )
    new_credential = InteractiveBrowserCredential(
        client_id=_CLIENT_ID,
        profile=new_profile,
        silent_auth_only=True
    )

We can at least make this less awkward in a future revision.

@chlowell chlowell force-pushed the profile-prototype branch 2 times, most recently from 3742edc to 848fe69 Compare April 7, 2020 21:59
if not token:
if self._silent_auth_only:
raise AuthenticationRequiredError()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add the MSAL error result in the error exception so that client side can get the fail reason when get the access token

@chlowell
Copy link
Owner Author

Thanks for all the feedback. This PR is superseded by Azure#10612.

Breaking changes between this and that are:

  • renames
    • AuthProfile -> AuthenticationRecord
    • keyword argument profile -> authentication_record
    • keyword argument silent_auth_only -> disable_automatic_authentication
  • authenticate is an instance method returning an AuthenticationRecord
  • reverted changes to UsernamePasswordCredential

There may be more before the merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants