Skip to content

Commit

Permalink
Friendly hint covers most flows, tested. TODO: OBO
Browse files Browse the repository at this point in the history
  • Loading branch information
rayluo committed Mar 21, 2024
1 parent edc06b9 commit 10370ca
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 11 deletions.
21 changes: 16 additions & 5 deletions msal/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,16 @@ def _build_telemetry_context(
self._telemetry_buffer, self._telemetry_lock, api_id,
correlation_id=correlation_id, refresh_reason=refresh_reason)

def _adjust_response(self, response): # Adjust response inline
# Currently, this is used to provide better error message for CIAM CUD
error_description = response.get("error_description", "")
if ("AADSTS500207" in error_description # Observed in most auth grants
or "AADSTS900144" in error_description # Observed in ROPC
) and self._oidc_authority and not self._oidc_authority.endswith("/v2.0"):
response["error_description"] = (
'Did you forget to append "/v2.0" to your oidc_authority? '
+ response["error_description"])

def _get_regional_authority(self, central_authority):
if not self._region_configured: # User did not opt-in to ESTS-R
return None # Short circuit to completely bypass region detection
Expand Down Expand Up @@ -974,11 +984,7 @@ def authorize(): # A controller in a web app
**kwargs))
if "access_token" in response:
response[self._TOKEN_SOURCE] = self._TOKEN_SOURCE_IDP
if ("AADSTS500207" in response.get("error_description", "") and
self._oidc_authority and not self._oidc_authority.endswith("/v2.0")):
response["error_description"] = (
'Did you forget to append "/v2.0" to your oidc_authority? '
+ response["error_description"])
self._adjust_response(response)
telemetry_context.update_telemetry(response)
return response

Expand Down Expand Up @@ -1706,6 +1712,7 @@ def acquire_token_by_username_password(
**kwargs))
if "access_token" in response:
response[self._TOKEN_SOURCE] = self._TOKEN_SOURCE_IDP
self._adjust_response(response)
telemetry_context.update_telemetry(response)
return response

Expand Down Expand Up @@ -2008,6 +2015,8 @@ def acquire_token_interactive(
**kwargs))
if "access_token" in response:
response[self._TOKEN_SOURCE] = self._TOKEN_SOURCE_IDP
self._adjust_response(response) # Note: It won't improve
# the error rendered in browser, but still better than nothing
telemetry_context.update_telemetry(response)
return response

Expand Down Expand Up @@ -2117,6 +2126,7 @@ def initiate_device_flow(self, scopes=None, **kwargs):
headers={msal.telemetry.CLIENT_REQUEST_ID: correlation_id},
**kwargs)
flow[self.DEVICE_FLOW_CORRELATION_ID] = correlation_id
self._adjust_response(flow) # AADSTS500207 would happen here, not at token endpoint
return flow

def acquire_token_by_device_flow(self, flow, claims_challenge=None, **kwargs):
Expand Down Expand Up @@ -2214,6 +2224,7 @@ def _acquire_token_for_client(
claims=_merge_claims_challenge_and_capabilities(
self._client_capabilities, claims_challenge)),
**kwargs)
self._adjust_response(response)
telemetry_context.update_telemetry(response)
return response

Expand Down
6 changes: 6 additions & 0 deletions msal/authority.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def __init__(

def _initialize_oidc_authority(self, oidc_authority_url):
authority, self.instance, tenant = canonicalize(oidc_authority_url)
self._is_oidc = True
self.is_adfs = tenant.lower() == 'adfs' # As a convention
self._is_b2c = True # Not exactly true, but
# OIDC Authority was designed for CIAM which is the next gen of B2C.
Expand All @@ -116,6 +117,7 @@ def _initialize_entra_authority(
# instance discovery endpoint located at ``login.microsoftonline.com``.
# You can customize the endpoint by providing a url as a string.
# Or you can turn this behavior off by passing in a False here.
self._is_oidc = False
if isinstance(authority_url, AuthorityBuilder):
authority_url = str(authority_url)
authority, self.instance, tenant = canonicalize(authority_url)
Expand Down Expand Up @@ -162,6 +164,10 @@ def user_realm_discovery(self, username, correlation_id=None, response=None):
# It will typically return a dict containing "ver", "account_type",
# "federation_protocol", "cloud_audience_urn",
# "federation_metadata_url", "federation_active_auth_url", etc.
if self._is_oidc:
# Conceptually, OIDC has no user real discovery.
# Besides, ROPC on CIAM CUD apparently works without the federation anyway
return {} # This can guide the caller to fall back normal ROPC flow
if self.instance not in self.__class__._domains_without_user_realm_discovery:
resp = response or self._http_client.get(
"https://{netloc}/common/userrealm/{username}?api-version=1.0".format(
Expand Down
6 changes: 5 additions & 1 deletion sample/confidential_client_certificate_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
{
"authority": "https://login.microsoftonline.com/Enter_the_Tenant_Name_Here",
// Usually you use this one
"oidc_authority": "https://contoso.com/Enter_the_Tenant_Name_Here",
// Alternatively, you use this one when your CIAM tenant has a custom domain
"client_id": "your_client_id came from https://learn.microsoft.com/entra/identity-platform/quickstart-register-app",
"scope": ["https://graph.microsoft.com/.default"],
// Specific to Client Credentials Grant i.e. acquire_token_for_client(),
Expand Down Expand Up @@ -50,7 +53,8 @@

# Create a preferably long-lived app instance, to avoid the overhead of app creation
global_app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
config["client_id"], authority=config.get("authority"),
oidc_authority=config.get("oidc_authority"), # Use this for a CIAM custom domain
client_credential={"thumbprint": config["thumbprint"], "private_key": open(config['private_key_file']).read()},
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
# If absent, ClientApplication will create its own empty token cache
Expand Down
6 changes: 5 additions & 1 deletion sample/confidential_client_secret_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
{
"authority": "https://login.microsoftonline.com/Enter_the_Tenant_Name_Here",
// Usually you use this one
"oidc_authority": "https://contoso.com/Enter_the_Tenant_Name_Here",
// Alternatively, you use this one when your CIAM tenant has a custom domain
"client_id": "your_client_id came from https://learn.microsoft.com/entra/identity-platform/quickstart-register-app",
"scope": ["https://graph.microsoft.com/.default"],
// Specific to Client Credentials Grant i.e. acquire_token_for_client(),
Expand Down Expand Up @@ -49,7 +52,8 @@

# Create a preferably long-lived app instance, to avoid the overhead of app creation
global_app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
config["client_id"], authority=config.get("authority"),
oidc_authority=config.get("oidc_authority"), # Use this for a CIAM custom domain
client_credential=config["secret"],
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
# If absent, ClientApplication will create its own empty token cache
Expand Down
6 changes: 5 additions & 1 deletion sample/device_flow_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
{
"authority": "https://login.microsoftonline.com/common",
// Usually you use this one
"oidc_authority": "https://contoso.com/Enter_the_Tenant_Name_Here",
// Alternatively, you use this one when your CIAM tenant has a custom domain
"client_id": "your_client_id came from https://learn.microsoft.com/entra/identity-platform/quickstart-register-app",
"scope": ["User.ReadBasic.All"],
// You can find the other permission names from this document
Expand Down Expand Up @@ -39,7 +42,8 @@

# Create a preferably long-lived app instance, to avoid the overhead of app creation
global_app = msal.PublicClientApplication(
config["client_id"], authority=config["authority"],
config["client_id"], authority=config.get("authority"),
oidc_authority=config.get("oidc_authority"), # Use this for a CIAM custom domain
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
# If absent, ClientApplication will create its own empty token cache
)
Expand Down
6 changes: 5 additions & 1 deletion sample/interactive_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
{
"authority": "https://login.microsoftonline.com/organizations",
// Usually you use this one
"oidc_authority": "https://contoso.com/Enter_the_Tenant_Name_Here",
// Alternatively, you use this one when your CIAM tenant has a custom domain
"client_id": "your_client_id came from https://learn.microsoft.com/entra/identity-platform/quickstart-register-app",
"scope": ["User.ReadBasic.All"],
// You can find the other permission names from this document
Expand Down Expand Up @@ -36,7 +39,8 @@

# Create a preferably long-lived app instance, to avoid the overhead of app creation
global_app = msal.PublicClientApplication(
config["client_id"], authority=config["authority"],
config["client_id"], authority=config.get("authority"),
oidc_authority=config.get("oidc_authority"), # Use this for a CIAM custom domain
#enable_broker_on_windows=True, # Opted in. You will be guided to meet the prerequisites, if your app hasn't already
# See also: https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token-wam#wam-value-proposition
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
Expand Down
9 changes: 7 additions & 2 deletions sample/username_password_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
{
"authority": "https://login.microsoftonline.com/organizations",
// Usually you use this one
"oidc_authority": "https://contoso.com/Enter_the_Tenant_Name_Here",
// Alternatively, you use this one when your CIAM tenant has a custom domain
"client_id": "your_client_id came from https://learn.microsoft.com/entra/identity-platform/quickstart-register-app",
"username": "your_username@your_tenant.com",
"scope": ["User.ReadBasic.All"],
Expand Down Expand Up @@ -33,7 +36,8 @@
# logging.getLogger("msal").setLevel(logging.INFO) # Optionally disable MSAL DEBUG logs

config = json.load(open(sys.argv[1]))
config["password"] = getpass.getpass()
config["password"] = getpass.getpass(
prompt="Enter password for {}: ".format(config["username"]))

# If for whatever reason you plan to recreate same ClientApplication periodically,
# you shall create one global token cache and reuse it by each ClientApplication
Expand All @@ -44,7 +48,8 @@
global_app = msal.ClientApplication(
config["client_id"],
client_credential=config.get("client_secret"),
authority=config["authority"],
authority=config.get("authority"),
oidc_authority=config.get("oidc_authority"), # Use this for a CIAM custom domain
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
# If absent, ClientApplication will create its own empty token cache
)
Expand Down

0 comments on commit 10370ca

Please sign in to comment.