MSALPYTHON(1)

πŸ’– MSALPYTHON πŸ’–

okay bestie, welcome to msalpython β€” the MSAL Python Documentation ✨ (yes the manpage name is MSALPYTHON(1), we're fancy like that πŸ’…).

You can find high level conceptual documentations in the project README https://github.com/AzureAD/microsoft-authentication-library-for-python.


πŸ—ΊοΈ SCENARIOS

There are many different application scenarios https://docs.microsoft.com/azure/active-directory/develop/authentication-flows-app-scenarios. MSAL Python supports some of them. The following diagram serves as a map. Locate your application scenario on the map. If the corresponding icon is clickable, it will bring you to an MSAL Python sample for that scenario. πŸ¦‹


πŸ“š API REFERENCE

πŸ’¬ Note

Only the contents inside this source file https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/dev/msal/__init__.py and their documented methods (unless otherwise marked as deprecated) are MSAL Python public API, which are guaranteed to be backward-compatible until the next major version. Everything else, regardless of their naming, are all internal helpers, which could change at anytime in the future, without prior notice. 😭 read that again. at anytime. without prior notice. ngl that's the whole vibe.

The following section is the API Reference of MSAL Python. The API Reference is like a dictionary, which is useful when:

  • You already followed our sample(s) above and have your app up and running, but want to know more on how you could tweak the authentication experience by using other optional parameters (there are plenty of them! πŸ“)
  • Some important features have their in-depth documentations in the API Reference.

MSAL proposes a clean separation between public client applications and confidential client applications https://tools.ietf.org/html/rfc6749#section-2.1.

They are implemented as two separated classes, with different methods for different authentication scenarios. two whole separate girlies fr fr. πŸ’…


🌟 ClientApplication

class msal.ClientApplication(client_id, client_credential=None,
authority=None, validate_authority=True, token_cache=None,
http_client=None, verify=True, proxies=None, timeout=None,
client_claims=None, app_name=None, app_version=None,
client_capabilities=None, azure_region=None, exclude_scopes=None,
http_cache=None, instance_discovery=None, allow_broker=None,
enable_pii_log=None, oidc_authority=None)

You do not usually directly use this class. Use its subclasses instead: PublicClientApplication and ConfidentialClientApplication. (This one is giving "abstract parent who does all the work behind the scenes" and honestly? iconic. πŸ’œ)

__init__(client_id, client_credential=None, authority=None,
validate_authority=True, token_cache=None, http_client=None,
verify=True, proxies=None, timeout=None, client_claims=None,
app_name=None, app_version=None, client_capabilities=None,
azure_region=None, exclude_scopes=None, http_cache=None,
instance_discovery=None, allow_broker=None, enable_pii_log=None,
oidc_authority=None)

Create an instance of application.

πŸ“ Parameters

  • client_id (str) β€” Your app has a client_id after you register it on Microsoft Entra admin center.
  • client_credential (Union[dict, str, None]) β€”

For PublicClientApplication, you use None here.

For ConfidentialClientApplication, it supports many different input formats for different scenarios. okay so listen, this parameter has ✨range✨:

  • Support using a client secret.

Just feed in a string, such as "your client secret".

  • Support using a certificate in X.509 (.pem) format
Deprecated because it uses SHA-1 thumbprint, unless you are still using ADFS which supports SHA-1 thumbprint only. Please use the .pfx option documented later in this page.

Feed in a dict in this form:

{
    "private_key": "...-----BEGIN PRIVATE KEY-----... in PEM format",
    "thumbprint": "An SHA-1 thumbprint such as A1B2C3D4E5F6..."
        "Changed in version 1.35.0, if thumbprint is absent"
        "and a public_certificate is present, MSAL will"
        "automatically calculate an SHA-256 thumbprint instead.",
    "passphrase": "Needed if the private_key is encrypted (Added in version 1.6.0)",
    "public_certificate": "...-----BEGIN CERTIFICATE-----...",  # Needed if you use Subject Name/Issuer auth. Added in version 0.5.0.
}

MSAL Python requires a "private_key" in PEM format. If your cert is in PKCS12 (.pfx) format, you can convert it to X.509 (.pem) format, by openssl pkcs12 -in file.pfx -out file.pem -nodes.

The thumbprint is available in your app's registration in Azure Portal. Alternatively, you can calculate the thumbprint https://github.com/Azure/azure-sdk-for-python/blob/07d10639d7e47f4852eaeb74aef5d569db499d6e/sdk/identity/azure-identity/azure/identity/_credentials/certificate.py#L94-L97.

public_certificate (optional) is public key certificate which will be sent through 'x5c' JWT header. This is useful when you use Subject Name/Issuer Authentication https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/60 which is an approach to allow easier certificate rotation. Per specs https://tools.ietf.org/html/rfc7515#section-4.1.6, "the certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one." However, your certificate's issuer may use a different order. So, if your attempt ends up with an error AADSTS700027 β€” "The provided signature value did not match the expected signature value", you may try use only the leaf cert (in PEM/str format) instead. πŸ’€ (yes this is a real trap, we're warning you)

  • Supporting raw assertion obtained from elsewhere

Added in version 1.13.0: It can also be a completely pre-signed assertion that you've assembled yourself. Simply pass a container containing only the key "client_assertion", like this:

{
    "client_assertion": "...a JWT with claims aud, exp, iss, jti, nbf, and sub..."
}
Note: A pre-signed JWT string has a fixed expiration. Long-running confidential client applications (for example, workloads using AKS workload identity federation, or any other dynamic credential source) should instead pass a callable which MSAL will invoke on demand to obtain a fresh assertion: python def get_client_assertion(): # e.g. read the projected service-account token from disk with open("/var/run/secrets/azure/tokens/azure-identity-token") as f: return f.read() app = ConfidentialClientApplication( "client_id", client_credential={"client_assertion": get_client_assertion}, ..., ) The callable is only invoked when MSAL needs to send a token request on the wire (the in-memory token cache transparently avoids unnecessary calls). lowkey so smart 🧚 If your callback is itself expensive (for example it calls out to a key vault), wrap it in msal.AutoRefresher to memoize the assertion for its lifetime: python from msal import AutoRefresher smart_callback = AutoRefresher(get_client_assertion, expires_in=3600) app = ConfidentialClientApplication( "client_id", client_credential={"client_assertion": smart_callback}, ..., ) Passing a plain str / bytes client_assertion is still supported for backward compatibility but is discouraged because the assertion will eventually expire. πŸ™ˆ
  • Supporting reading client certificates from PFX files

This is giving lazy-girl certificate flex, honestly. ✨ Type the passphrase for an encrypted private key as a third item in that list, and boom, MSAL hits the shortcut β€” it'll just auto-use the SHA-256 thumbprint of your cert instead of making you do it yourself. πŸ’…

Added in version 1.29.0: you can also feed in a dictionary containing the path to a PFX file:

{
    "private_key_pfx_path": "/path/to/your.pfx",  # Added in version 1.29.0
    "public_certificate": True,  # Only needed if you use Subject Name/Issuer auth. Added in version 1.30.0
    "passphrase": "Passphrase if the private_key is encrypted (Optional)",
}

And this next command will generate a .pfx file from your .key and .pem file, no stress bestie:

openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.pem

Subject Name/Issuer Auth (link goes to https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/60) is literally just a whole approach that makes certificate rotation easier. So if your .pfx file has both the private key and the public cert in it, you can opt into Subject Name/Issuer Auth by setting "public_certificate" to True. Slay. πŸŽ€

  • client_claims (dict) β€” Added in version 0.5.0: it's a dictionary of extra claims that get signed by the ConfidentialClientApplication's private key. Think {"client_ip": "x.x.x.x"} energy. πŸ‘‘ You can also override any of these default claims, if you're feeling bold:
{
    "aud": the_token_endpoint,
    "iss": self.client_id,
    "sub": same_as_issuer,
    "exp": now + 10_min,
    "iat": now,
    "jti": a_random_uuid
}

Changed in version 1.17: you can also use a predefined constant and a builder, like this:

from msal.authority import (
    AuthorityBuilder,
    AZURE_US_GOVERNMENT, AZURE_CHINA, AZURE_PUBLIC)
my_authority = AuthorityBuilder(AZURE_PUBLIC, "contoso.onmicrosoft.com")
# Now you get an equivalent of
# "https://login.microsoftonline.com/contoso.onmicrosoft.com"

# You can feed such an authority to msal's ClientApplication
from msal import PublicClientApplication
app = PublicClientApplication("my_client_id", authority=my_authority, ...)
  • validate_authority (bool) β€” optional! Turns authority validation on or off. Defaults to true.
  • token_cache (TokenCache) β€” sets the token cache that this ClientApplication instance uses. No config? Bestie, it just makes an in-memory cache and uses that. 🫢
  • http_client β€” optional! Your very own implementation of the abstract class HttpClient (see msal.oauth2cli.http.http_client). Defaults to a requests session instance. Since MSAL 1.11.0, that default session is set up to attempt one retry on connection error. But heads up: if you're bringing your own http_client, then deciding whether to retry is your http_client's job. Not ours. Periodt. πŸ’…
  • verify β€” optional! This gets passed to the verify parameter in the underlying requests library (docs at http://docs.python-requests.org/en/v2.9.1/user/advanced/#ssl-cert-verification). Doesn't apply at all if you chose to pass your own HTTP client, jsyk.
  • proxies β€” optional! This gets passed to the proxies parameter in the underlying requests library (docs at http://docs.python-requests.org/en/v2.9.1/user/advanced/#proxies). Also doesn't apply if you passed your own HTTP client. πŸ™ˆ
  • timeout β€” optional! Passed to the timeout parameter in the underlying requests library (docs at http://docs.python-requests.org/en/v2.9.1/user/advanced/#timeouts). And yeah, same deal β€” it doesn't apply if you passed your own HTTP client. 😭
  • app_name β€” optional! Drop your application name here for Microsoft telemetry purposes. Default value is None, which means it won't be sent to Microsoft at all. πŸ“
  • app_version β€” optional! Same vibe, but for your app's version, also for Microsoft telemetry. Default is None, also meaning it's not passed to Microsoft. ✨
  • client_capabilities (list[str]) β€” optional! Lets you configure one or more client capabilities, e.g. ["CP1"].

Client capability is basically MSAL telling the Microsoft identity platform (STS) what this client is capable of, so STS can decide to turn certain features on. Like, if the client can handle a claims challenge, STS may hand out Continuous Access Evaluation (CAE) access tokens to resources (docs at https://learn.microsoft.com/entra/identity/conditional-access/concept-continuous-access-evaluation) β€” because it knows that when the resource emits a claims challenge, this client can actually handle it. Main character capability energy, honestly. πŸ‘‘

Implementation details, for the curious: client capability is currently implemented using the "claims" parameter on the wire. MSAL will combine them into the claims parameter (spec at https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter), which you'll later provide via one of the acquire-token requests.

  • azure_region (str) β€” optional! Tells MSAL to use the Entra regional token service. Heads up though: this is a legacy feature and it's only available to first-party applications. Only acquire_token_for_client() is supported. 🌸

It supports these 4 values:

  1. azure_region=None β€” the default, meaning no region is configured. MSAL will use the region defined in the env var MSAL_FORCE_REGION.
  2. azure_region="some_region" β€” meaning the specified region gets used.
  3. azure_region=True β€” meaning MSAL will try to auto-detect the region. This is not recommended. πŸ’€
  4. azure_region=False β€” meaning MSAL will use no region.
Note: region auto-discovery has been tested on VMs and on Azure Functions. It is unreliable. Applications using this option should configure a short timeout.

For more details, and for the actual values of the region string, see https://learn.microsoft.com/entra/msal/dotnet/resources/region-discovery-troubleshooting.

New in version 1.12.0.

  • exclude_scopes (list[str]) β€” optional! Historically MSAL hardcodes the offline_access scope, which would let your app have prolonged access to the user's data. If that's unnecessary or undesirable for your app, now you can use this parameter to supply an exclusion list of scopes, like exclude_scopes = ["offline_access"]. 🧚
  • http_cache (dict) β€” okay so MSAL has been caching tokens in the token_cache for a long time. Recently, MSAL also introduced the concept of an http_cache, by automatically caching some finite amount of non-token HTTP responses β€” so that long-lived PublicClientApplication and ConfidentialClientApplication instances are more performant and responsive in some situations. πŸ”₯

This http_cache parameter accepts any dict-like object. If you don't provide one, MSAL will just use an in-memory dict. πŸ’–

If your app is a command-line app (CLI), you'd want your http_cache to survive across different CLI runs β€” like, actually persist, bestie. πŸ’Ύ But heads up: the persisted file's format may change due to, but not limited to, unstable protocol <https://docs.python.org/3/library/pickle.html#data-stream-format>, so your implementation shall tolerate unexpected loading errors. Lowkey non-negotiable. Here's the recipe that does it:

# Just add the following lines at the beginning of your CLI script
import sys, atexit, pickle, logging
http_cache_filename = sys.argv[0] + ".http_cache"
try:
    with open(http_cache_filename, "rb") as f:
        persisted_http_cache = pickle.load(f)  # Take a snapshot
except (
        FileNotFoundError,  # Or IOError in Python 2
        pickle.UnpicklingError,  # A corrupted http cache file
        AttributeError,  # Cache created by a different version of MSAL
        ):
    persisted_http_cache = {}  # Recover by starting afresh
except:  # Unexpected exceptions
    logging.exception("You may want to debug this")
    persisted_http_cache = {}  # Recover by starting afresh
atexit.register(lambda: pickle.dump(
    # When exit, flush it back to the file.
    # It may occasionally overwrite another process's concurrent write,
    # but that is fine. Subsequent runs will reach eventual consistency.
    persisted_http_cache, open(http_cache_file, "wb")))

# And then you can implement your app as you normally would
app = msal.PublicClientApplication(
    "your_client_id",
    ...,
    http_cache=persisted_http_cache,  # Utilize persisted_http_cache
    ...,
    #token_cache=...,  # You may combine the old token_cache trick
        # Please refer to token_cache recipe at
        # https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache
    )
app.acquire_token_interactive(["your", "scope"], ...)

✨ Content inside http_cache is cheap to obtain. There's literally no need to share them among different apps. Not the vibe.

🫢 Content inside http_cache will contain no tokens and no Personally Identifiable Information (PII). Encryption is unnecessary, so don't stress it, okay?

New in version 1.16.0.
  • instance_discovery (boolean) β€” okay so history time πŸ“œ: back in the day, MSAL would connect to a central endpoint at https://login.microsoftonline.com to acquire some metadata, especially when using an unfamiliar authority. This behavior is known as Instance Discovery.

This parameter defaults to None, which enables the Instance Discovery.

If you know some authorities which you allow MSAL to operate with as-is, without involving any Instance Discovery, the recommended pattern is:

known_authorities = frozenset([  # Treat your known authorities as const
    "https://contoso.com/adfs", "https://login.azs/foo"])
...
authority = "https://contoso.com/adfs"  # Assuming your app will use this
app1 = PublicClientApplication(
    "client_id",
    authority=authority,
    # Conditionally disable Instance Discovery for known authorities
    instance_discovery=authority not in known_authorities,
    )

If you do not know some authorities beforehand, yet still want MSAL to accept any authority that you will provide, you can use a False to unconditionally disable Instance Discovery.

New in version 1.19.0.
  • allow_broker (boolean) β€” Deprecated. Please use enable_broker_on_windows instead. It's giving legacy, bestie. πŸ’€
  • enable_pii_log (boolean) β€” when enabled, logs may include PII (Personal Identifiable Information). This can be useful in troubleshooting broker behaviors. The default behavior is False.
New in version 1.24.0.
  • oidc_authority (str) β€” Added in version 1.28.0: it's a URL that identifies an OpenID Connect (OIDC) authority of the format https://contoso.com/tenant. MSAL will append ".well-known/openid-configuration" to the authority and retrieve the OIDC metadata from there, to figure out the endpoints. 🧚
Note: Broker will NOT be used for OIDC authority. No broker for you, sorry queen. πŸ™ˆ

πŸ’… acquire_token_by_auth_code_flow(auth_code_flow, auth_response, scopes=None, **kwargs)

Validate the auth response being redirected back, and obtain tokens. It automatically provides nonce protection β€” slay. πŸ”₯

Parameters

  • auth_code_flow (dict) β€” the same dict returned by initiate_auth_code_flow().
  • auth_response (dict) β€” a dict of the query string received from auth server.
  • scopes (list[str]) β€” scopes requested to access a protected API (a resource).

Most of the time, you can leave it empty. Like, genuinely.

If you requested user consent for multiple resources, here you will need to provide a subset of what you required in initiate_auth_code_flow().

OAuth2 was designed mostly for singleton services, where tokens are always meant for the same resource and the only changes are in the scopes. In Microsoft Entra, tokens can be issued for multiple 3rd party resources. You can ask authorization code for multiple resources, but when you redeem it, the token is for only one intended recipient, called audience. So the developer need to specify a scope so that we can restrict the token to be issued for the corresponding audience. Periodt. πŸ‘‘

Returns

def authorize():  # A controller in a web app
    try:
        result = msal_app.acquire_token_by_auth_code_flow(
            session.get("flow", {}), request.args)
        if "error" in result:
            return render_template("error.html", result)
        use(result)  # Token(s) are available in result and cache
    except ValueError:  # Usually caused by CSRF
        pass  # Simply ignore them
    return redirect(url_for("index"))

πŸ¦‹ acquire_token_by_authorization_code(code, scopes, redirect_uri=None, nonce=None, claims_challenge=None, **kwargs)

The second half of the Authorization Code Grant. The sequel we all deserved. πŸŽ€

Parameters

  • code β€” the authorization code returned from Authorization Server.
  • scopes (list[str]) β€” (Required) scopes requested to access a protected API (a resource).

If you requested user consent for multiple resources, here you will typically want to provide a subset of what you required in AuthCode.

OAuth2 was designed mostly for singleton services, where tokens are always meant for the same resource and the only changes are in the scopes. In Microsoft Entra, tokens can be issued for multiple 3rd party resources. You can ask authorization code for multiple resources, but when you redeem it, the token is for only one intended recipient, called audience. So the developer need to specify a scope so that we can restrict the token to be issued for the corresponding audience. Same energy as above, iykyk. πŸ’–

  • nonce β€” if you handed over a nonce when you called get_authorization_request_url(), you've gotta give that exact same nonce here too, so we can validate it. And bestie, if the nonce in the id token doesn't match, an exception gets raised. Periodt. πŸ’…
  • claims_challenge β€” okay so this one asks for specific claims that the resource provider wants, delivered as a claims_challenge directive inside the www-authenticate header, and they come back from the UserInfo Endpoint and/or in the ID Token and/or Access Token. It's a string of a JSON object that holds lists of the claims being requested from those places. It's giving very structured, very ✨specified✨.

Returns 🌈

A dict representing the json response from Microsoft Entra:

  • A successful response would contain "access_token" key,
  • an error response would contain "error" and usually "error_description".

acquire_token_by_refresh_token(refresh_token, scopes, **kwargs) πŸ¦‹

Grab token(s) based on a refresh token (RT) you got from somewhere else.

You use this method only when you have old RTs from elsewhere and you wanna migrate them into MSAL, like bringing your whole wardrobe to a new closet. Calling this method results in new tokens automatically storing into MSAL. So convenient, no cap.

You do NOT need to use this method if you are already using MSAL. MSAL maintains RT automatically inside its token cache, and an access token can be retrieved when you call acquire_token_silent(). It's already handling it, bestie, chill.

Parameters

Returns

  • A dict contains "error" and some other keys, when error happened.
  • A dict contains no "error" key means migration was successful. πŸŽ‰

acquire_token_by_username_password(username, password, scopes, claims_challenge=None, auth_scheme=None, **kwargs) πŸŽ€

Gets a token for a given resource via user credentials.

See this page for constraints of Username Password Flow. https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki/Username-Password-Authentication

Parameters

  • username (str) β€” Typically a UPN in the form of an email address.
  • password (str) β€” The password.
  • scopes (list[str]) β€” Scopes requested to access a protected API (a resource).
  • claims_challenge β€” The claims_challenge parameter requests specific claims requested by the resource provider in the form of a claims_challenge directive in the www-authenticate header to be returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. It is a string of a JSON object which contains lists of claims being requested from these locations.
  • auth_scheme (object) β€”

You can provide an msal.auth_scheme.PopAuthScheme object so that MSAL will get a Proof-of-Possession (POP) token for you. Iconic behavior honestly.

New in version 1.26.0.

Returns

A dict representing the json response from Microsoft Entra:

  • A successful response would contain "access_token" key,
  • an error response would contain "error" and usually "error_description".
[Deprecated] This API is deprecated for public client flows and will be removed in a future release. Use a more secure flow instead. ⚠️ Like, it's on the way out, bestie, don't build your whole personality on it. Migration guide: https://aka.ms/msal-ropc-migration

acquire_token_silent(scopes, account, authority=None, force_refresh=False, claims_challenge=None, auth_scheme=None, **kwargs) πŸ’œ

Acquire an access token for given account, without user interaction. No popups, no redirects, just vibes.

It has same parameters as the acquire_token_silent_with_error(). The difference is the behavior of the return value. This method will combine the cache empty and refresh error into one return value, None. So if your app does not care about the exact token refresh error during token cache look-up, then this method is easier and recommended. Less drama, same result. 🫢

Returns

  • A dict containing no "error" key, and typically contains an "access_token" key, if cache lookup succeeded.
  • None when cache lookup does not yield a token.

acquire_token_silent_with_error(scopes, account, authority=None, force_refresh=False, claims_challenge=None, auth_scheme=None, **kwargs) ✨

Acquire an access token for given account, without user interaction.

It is done either by finding a valid access token from cache, or by finding a valid refresh token from cache and then automatically use it to redeem a new access token. Either way, you're getting your token, slay.

This method will differentiate cache empty from token refresh error. If your app cares the exact token refresh error during token cache look-up, then this method is suitable. Otherwise, the other method acquire_token_silent() is recommended. It's the lowkey gossipy version that tells you exactly what went wrong. 😭

Parameters

  • scopes (list[str]) β€” (Required) Scopes requested to access a protected API (a resource).
  • account β€” (Required) One of the account object returned by get_accounts(). Starting from MSAL Python 1.23, a None input will become a NO-OP and always return None.
  • force_refresh β€” If True, it will skip Access Token look-up, and try to find a Refresh Token to obtain a new Access Token.
  • claims_challenge β€” The claims_challenge parameter requests specific claims requested by the resource provider in the form of a claims_challenge directive in the www-authenticate header to be returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. It is a string of a JSON object which contains lists of claims being requested from these locations.
  • auth_scheme (object) β€”

You can provide an msal.auth_scheme.PopAuthScheme object so that MSAL will get a Proof-of-Possession (POP) token for you.

New in version 1.26.0.

Returns

  • A dict containing no "error" key, and typically contains an "access_token" key, if cache lookup succeeded.
  • None when there is simply no token in the cache.
  • A dict containing an "error" key, when token refresh failed.

get_accounts(username=None) 🌸

Get a list of accounts which previously signed in, i.e. exists in cache. Your little roster of everyone who ever showed up. πŸ‘‘

An account can later be used in acquire_token_silent() to find its tokens.

Parameters

username β€” Filter accounts with this username only. Case insensitive. Don't worry about the caps, it doesn't care either.

Returns

A list of account objects. Each account is a dict. For now, we only document its "username" field. Your app can choose to display those information to end user, and allow user to choose one of his/her accounts to proceed. Very choose-your-own-adventure of you.


get_authorization_request_url(scopes, login_hint=None, state=None, redirect_uri=None, response_type='code', prompt=None, nonce=None, domain_hint=None, claims_challenge=None, **kwargs) πŸ”₯

Constructs a URL for you to start a Authorization Code Grant.

Parameters

  • scopes (list[str]) β€” (Required) Scopes requested to access a protected API (a resource).
  • state (str) β€” Recommended by OAuth2 for CSRF protection.
  • login_hint (str) β€” Identifier of the user. Generally a User Principal Name (UPN).
  • redirect_uri (str) β€” Address to return to upon receiving a response from the authority.
  • response_type (str) β€”

Default value is "code" for an OAuth2 Authorization Code grant. The classic, the standard, the moment. πŸ’…

You could use other content such as "id_token" or "token", which would trigger an Implicit Grant, but that is not recommended https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow#is-the-implicit-grant-suitable-for-my-app.

Returns

The authorization url as a string. That's it, that's the whole return. πŸ¦‹


initiate_auth_code_flow(scopes, redirect_uri=None, state=None, prompt=None, login_hint=None, domain_hint=None, claims_challenge=None, max_age=None, response_mode=None)

initiate_auth_code_flow(scopes, redirect_uri=None, state=None, prompt=None, login_hint=None,
                        domain_hint=None, claims_challenge=None, max_age=None, response_mode=None)

Kicks off an auth code flow. Bestie, pay attention: later, when the response actually reaches your redirect_uri, you use acquire_token_by_auth_code_flow() to finish the authentication/authorization. That's the two-step. 🫢

Parameters

  • scopes (list) β€” a list of case-sensitive strings. Case. Sensitive. Not joking. πŸ‘‘
  • redirect_uri (str) β€” optional. If you don't specify it, the server will use the pre-registered one.
  • state (str) β€” an opaque value the client uses to keep state between the request and the callback. If it's absent, this library will automatically generate one internally for you. 🧚
  • prompt (str) β€” by default, no prompt value gets sent, not even the string "none". You have to specify a value explicitly. Its valid values are the constants defined in Prompt.
  • login_hint (str) β€” optional. Identifier of the user. Generally a User Principal Name (UPN).
  • domain_hint β€”

Can be one of "consumers" or "organizations" or your tenant domain "contoso.com". If included, it skips the email-based discovery process that user goes through on the sign-in page, leading to a slightly more streamlined user experience. More information on possible values available in the Auth Code Flow doc (https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code) and the domain_hint doc (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oapx/86fb452d-e34a-494e-ac61-e526e263b6d8). ✨

  • max_age (int) β€”

OPTIONAL. Maximum Authentication Age. Specifies the allowable elapsed time in seconds since the last time the End-User was actively authenticated. If the elapsed time is greater than this value, Microsoft identity platform will actively re-authenticate the End-User.

MSAL Python will also automatically validate the auth_time in ID token.

New in version 1.15. 🌈

  • response_mode (str) β€”

OPTIONAL. Specifies the method with which response parameters should be returned. The default value is equivalent to query, which was still secure enough in MSAL Python (because MSAL Python does not transfer tokens via query parameter in the first place). For even better security, we recommend using the value form_post. In "form_post" mode, response parameters will be encoded as HTML form values that are transmitted via the HTTP POST method and encoded in the body using the application/x-www-form-urlencoded format. Valid values can be either "form_post" for HTTP POST to callback URI or "query" (the default) for HTTP GET with parameters encoded in query string. More information on possible values here (https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes) and here (https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html#FormPostResponseMode).

Note: You should configure your web framework to accept form_post responses instead of query responses. While this parameter still works, it will be removed in a future version. Using query-based response modes is less secure and should be avoided. πŸ’€ Like genuinely, please don't.

Returns

The auth code flow. It is a dict shaped like this:

{
    "auth_uri": "https://...",  // Guide user to visit this
    "state": "...",  // You may choose to verify it by yourself,
                     // or just let acquire_token_by_auth_code_flow()
                     // do that for you.
    "...": "...",  // Everything else are reserved and internal
}

The caller is expected to:

  1. somehow store this content, typically inside the current session,
  2. guide the end user (i.e. resource owner) to visit that auth_uri,
  3. and then relay this dict and subsequent auth response to acquire_token_by_auth_code_flow().

is_pop_supported()

Returns True if this client supports Proof-of-Possession Access Token. πŸŽ€


remove_account(account)

Sign me out and forget me from token cache. Very "we are never ever getting back together" of you. πŸ’…


πŸ‘‘ PublicClientApplication πŸ‘‘

class msal.PublicClientApplication(client_id, client_credential=None, *, enable_broker_on_windows=None, enable_broker_on_mac=None, enable_broker_on_linux=None, enable_broker_on_wsl=None, **kwargs)

CONSOLE_WINDOW_HANDLE = <object object>


__init__(client_id, client_credential=None, *, enable_broker_on_windows=None, enable_broker_on_mac=None, enable_broker_on_linux=None, enable_broker_on_wsl=None, **kwargs)

__init__(client_id, client_credential=None, *, enable_broker_on_windows=None,
         enable_broker_on_mac=None, enable_broker_on_linux=None, enable_broker_on_wsl=None, **kwargs)

Same as ClientApplication.__init__(), except that client_credential parameter shall remain None. Periodt.

Note: What is a broker, and why use it? πŸ¦‹ A broker is a component installed on your device. Broker implicitly gives your device an identity. By using a broker, your device becomes a factor that can satisfy MFA (Multi-factor authentication). This factor would become mandatory if a tenant's admin enables a corresponding Conditional Access (CA) policy. The broker's presence allows Microsoft identity platform to have higher confidence that the tokens are being issued to your device, and that is more secure. πŸ”₯ An additional benefit of broker is, it runs as a long-lived process with your device's OS, and maintains its own cache, so that your broker-enabled apps (even a CLI) could automatically SSO from a previously established signed-in session. Lowkey iconic. How to opt in to use broker? 1. You can set any combination of the following opt-in parameters to true:

The original page lays this next bit out as a table, and the columns are:

  • Opt-in flag
  • If app will run on
  • App has registered this as a Desktop platform redirect URI in Azure Portal
and bestie, the table itself keeps going in the next part β€” hold that thought, don't go anywhere 🌸

…and the rest of that table, bestie, she's right here πŸ’… the redirect URIs your broker flags are gonna need:

  • enable_broker_on_windows β€” for Windows 10+ β†’ ms-appx-web://Microsoft.AAD.BrokerPlugin/your_client_id
  • enable_broker_on_wsl β€” for WSL β†’ ms-appx-web://Microsoft.AAD.BrokerPlugin/your_client_id
  • enable_broker_on_mac β€” for Mac with Company Portal installed β†’ msauth.com.msauth.unsignedapp://auth
  • enable_broker_on_linux β€” for Linux with Intune installed β†’ https://login.microsoftonline.com/common/oauth2/nativeclient (MUST be enabled, like actually)
  1. Install the broker dependency fr fr, e.g. pip install msal[broker]>=1.33,<2.
  2. Test it with acquire_token_interactive() and acquire_token_silent(). That's the vibe check 🫢

<h2>## The fallback behaviors of MSAL Python's broker support πŸ¦‹</h2>

MSAL will either error out, or silently fallback to non-broker flows. No in-between, she picks a lane:

  1. MSAL will ignore the enable_broker_... and bypass broker on those auth flows that are known to be NOT supported by broker. This includes ADFS, B2C, etc.. For other "could-use-broker" scenarios, please see below πŸ‘€
  2. MSAL errors out when app developer opted-in to use broker but a direct dependency "mid-tier" package is not installed. The error message guides the app developer to declare the correct dependency msal[broker]. We error out here because the error is actionable to app developers. Periodt.
  3. MSAL silently "deactivates" the broker and fallback to non-broker, when opted-in, dependency installed yet failed to initialize. We anticipate this would happen on a device whose OS is too old or the underlying broker component is somehow unavailable 😭 There is not much an app developer or the end user can do here. Eventually, the conditional access policy shall force the user to switch to a different device.
  4. MSAL errors out when broker is opted in, installed, initialized, but subsequent token request(s) failed. Yeah. That one's on the token request πŸ’€

<h3>### Parameters ✨</h3>

  • enable_broker_on_windows (boolean) β€” This setting is only effective if your app is running on Windows 10+. This parameter defaults to None, which means MSAL will not utilize a broker. New in MSAL Python 1.25.0.
  • enable_broker_on_mac (boolean) β€” This setting is only effective if your app is running on Mac. This parameter defaults to None, which means MSAL will not utilize a broker. New in MSAL Python 1.31.0.
  • enable_broker_on_linux (boolean) β€” This setting is only effective if your app is running on Linux, including WSL. This parameter defaults to None, which means MSAL will not utilize a broker. New in MSAL Python 1.33.0.
  • enable_broker_on_wsl (boolean) β€” This setting is only effective if your app is running on WSL. This parameter defaults to None, which means MSAL will not utilize a broker. New in MSAL Python 1.33.0.

<h2>## acquire_token_by_device_flow πŸŽ€</h2>

acquire_token_by_device_flow(flow, claims_challenge=None, **kwargs)

Obtain token by a device flow object, with customizable polling effect. It's giving "I'll check back in a sec" energy, but make it tokens 🌸

<h3>### Parameters πŸ’œ</h3>

  • flow (dict) β€” A dict previously generated by initiate_device_flow(). By default, this method's polling effect will block the current thread. You can abort the polling loop at any time, by changing the value of the flow's "expires_at" key to 0. (Yes, literally just set it to 0. Iconic little escape hatch ✨)
  • claims_challenge β€” The claims_challenge parameter requests specific claims requested by the resource provider in the form of a claims_challenge directive in the www-authenticate header to be returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. It is a string of a JSON object which contains lists of claims being requested from these locations.

<h3>### Returns 🫢</h3>

A dict representing the json response from Microsoft Entra:

  • A successful response would contain "access_token" key,
  • an error response would contain "error" and usually "error_description".

<h2>## acquire_token_interactive 🌈</h2>

acquire_token_interactive(scopes, prompt=None, login_hint=None,
domain_hint=None, claims_challenge=None, timeout=None, port=None,
extra_scopes_to_consent=None, max_age=None,
parent_window_handle=None, on_before_launching_ui=None,
auth_scheme=None, **kwargs)

Acquire token interactively i.e. via a local browser. Main character energy: your user pops open a browser, does their little sign-in moment, and comes back with a token πŸ‘‘

Prerequisite: In Azure Portal, configure the Redirect URI of your "Mobile and Desktop application" as http://localhost. If you opts in to use broker during PublicClientApplication creation, your app also need this Redirect URI: ms-appx-web://Microsoft.AAD.BrokerPlugin/YOUR_CLIENT_ID

<h3>### Parameters 🌸</h3>

  • scopes (list) β€” It is a list of case-sensitive strings. Mind the case, bestie, she's sensitive πŸ’…
  • prompt (str) β€” By default, no prompt value will be sent, not even string "none". You will have to specify a value explicitly. Its valid values are the constants defined in Prompt.
  • login_hint (str) β€” Optional. Identifier of the user. Generally a User Principal Name (UPN).
  • domain_hint β€” Can be one of "consumers" or "organizations" or your tenant domain "contoso.com". If included, it will skip the email-based discovery process that user goes through on the sign-in page, leading to a slightly more streamlined user experience. More information on possible values available in Auth Code Flow doc https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code and domain_hint doc https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oapx/86fb452d-e34a-494e-ac61-e526e263b6d8.
  • claims_challenge β€” The claims_challenge parameter requests specific claims requested by the resource provider in the form of a claims_challenge directive in the www-authenticate header to be returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. It is a string of a JSON object which contains lists of claims being requested from these locations. (same deal as the device flow one, no cap)
  • timeout (int) β€” This method will block the current thread. This parameter specifies the timeout value in seconds. Default value None means wait indefinitely. Fr, indefinitely. She'll wait forever if you let her πŸ™ˆ
  • port (int) β€” The port to be used to listen to an incoming auth response. By default we will use a system-allocated port. (The rest of the redirect_uri is hard coded as http://localhost.)
  • extra_scopes_to_consent (list) β€” "Extra scopes to consent" is a concept only available in Microsoft Entra. It refers to other resources you might want to prompt to consent for, in the same interaction, but for which you won't get back a token for in this particular operation. Lowkey a consent-only side quest 🧚
  • max_age (int) β€”
  • OPTIONAL. Maximum Authentication Age. This one sets the allowable elapsed time, in seconds, since the last time the End-User was actively authenticated. If the elapsed time is greater than this value, Microsoft identity platform will actively re-authenticate the End-User. πŸ’«

And here's the slay part: MSAL Python will also automatically validate the auth_time in the ID token. So it's double-checked, no cap.

New in version 1.15.

  • parent_window_handle (int) β€” OPTIONAL. But like… conditionally optional, okay bestie? Here's the vibe:
    • If your app does not opt in to use broker, you do not need to provide a parent_window_handle here. πŸŽ€
    • If your app opts in to use broker, parent_window_handle is required. πŸ‘€
      • If your app is a GUI app running on Windows or Mac system, you are required to also provide its window handle, so that the sign-in window will pop up on top of your window. (Otherwise you get that cursed "where did my login box go" moment, and we don't gatekeep that pain. πŸ’€)
      • If your app is a console app running on Windows or Mac system, you can use a placeholder: PublicClientApplication.CONSOLE_WINDOW_HANDLE.
    • Most Python scripts are console apps. 🌸

New in version 1.20.0.

  • on_before_launching_ui (function) β€” A callback with the form of
lambda ui="xyz", **kwargs: print("A {} will be launched".format(ui))

where ui will be either "browser" or "broker". You can use it to inform your end user to expect a pop-up window. πŸͺŸβœ¨

New in version 1.20.0.

  • auth_scheme (object) β€” You can provide an msal.auth_scheme.PopAuthScheme object so that MSAL will get a Proof-of-Possession (POP) token for you. πŸ›‘οΈ iconic behaviour tbh.

New in version 1.26.0.

Returns

  • A dict containing no "error" key, and typically contains an "access_token" key. πŸ’–
  • A dict containing an "error" key, when token refresh failed. 😭

🌈 initiate_device_flow(scopes=None, *, claims_challenge=None, **kwargs)

initiate_device_flow(scopes=None, *, claims_challenge=None, **kwargs)

Initiate a Device Flow instance, which will be used in acquire_token_by_device_flow(). It's giving "start the vibe, finish it later". πŸ¦‹

Parameters

  • scopes (list[str]) β€” Scopes requested to access a protected API (a resource).

Returns

A dict representing a newly created Device Flow object.

  • A successful response would contain "user_code" key, among others ✨
  • an error response would contain some other readable key/value pairs.

πŸ’œ ConfidentialClientApplication πŸ’œ

class msal.ConfidentialClientApplication(client_id, client_credential=None, authority=None, validate_authority=True, token_cache=None, http_client=None, verify=True, proxies=None, timeout=None, client_claims=None, app_name=None, app_version=None, client_capabilities=None, azure_region=None, exclude_scopes=None, http_cache=None, instance_discovery=None, allow_broker=None, enable_pii_log=None, oidc_authority=None)

Same as ClientApplication.__init__(), except that allow_broker parameter shall remain None. fr fr, don't be handing it anything else. 🚫

🫢 acquire_token_by_user_federated_identity_credential(scopes, assertion, username=None, user_object_id=None, claims_challenge=None, **kwargs)

acquire_token_by_user_federated_identity_credential(scopes, assertion, username=None, user_object_id=None, claims_challenge=None, **kwargs)

Acquires a user-scoped token using the user_fic grant type.

This method exchanges a federated identity credential (typically an agent instance token from Leg 2 of the agent identity protocol) for a user-scoped access token, enabling an agent to act on behalf of a specific user. πŸ€–βž‘οΈπŸ‘€ main character agent energy.

Parameters

  • scopes (list[str]) β€” Scopes required by downstream API (a resource).
  • assertion (str) β€” The federated identity credential token (e.g. the instance token obtained from Leg 2 of the agent identity flow).
  • username (str) β€” The target user's UPN (User Principal Name). Mutually exclusive with user_object_id. πŸ‘― can't have both, bestie.
  • user_object_id (str) β€” The target user's Object ID. Mutually exclusive with username.
  • claims_challenge β€” The claims_challenge parameter requests specific claims requested by the resource provider in the form of a claims_challenge directive in the www-authenticate header to be returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. It is a string of a JSON object which contains lists of claims being requested from these locations.

Returns

A dict representing the json response from Microsoft Entra:

  • A successful response would contain "access_token" key, πŸ’…
  • an error response would contain "error" and usually "error_description".

⭐ acquire_token_for_client(scopes, claims_challenge=None, fmi_path=None, **kwargs)

acquire_token_for_client(scopes, claims_challenge=None, fmi_path=None, **kwargs)

Acquires token for the current confidential client, not for an end user. It's the app's own token, no human attached. πŸ‘‘

Since MSAL Python 1.23, it will automatically look for token from cache, and only send request to Identity Provider when cache misses. Caching ate, periodt. πŸ”₯

Parameters

  • scopes (list[str]) β€” (Required) Scopes requested to access a protected API (a resource).
  • claims_challenge β€” The claims_challenge parameter requests specific claims requested by the resource provider in the form of a claims_challenge directive in the www-authenticate header to be returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. It is a string of a JSON object which contains lists of claims being requested from these locations.
  • fmi_path (str) β€” Optional. The Federated Managed Identity (FMI) credential path. When provided, it is sent as the fmi_path parameter in the token request body, and the resulting token is cached separately so that different FMI paths do not share cached tokens. 🧚 Example usage:
result = cca.acquire_token_for_client(
    scopes=["api://resource/.default"],
    fmi_path="SomeFmiPath/FmiCredentialPath",
)

Returns

A dict representing the json response from Microsoft Entra:

  • A successful response would contain "access_token" key, ✨
  • an error response would contain "error" and usually "error_description".

🌸 acquire_token_on_behalf_of(user_assertion, scopes, claims_challenge=None, **kwargs)

acquire_token_on_behalf_of(user_assertion, scopes, claims_challenge=None, **kwargs)

Acquires token using on-behalf-of (OBO) flow.

The current app is a middle-tier service which was called with a token representing an end user. The current app can use such token (a.k.a. a user assertion) to request another token to access downstream web API, on behalf of that user. See detail docs here <https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow> . πŸ“š

The current middle-tier app has no user interaction to obtain consent. See how to gain consent upfront for your middle-tier app from this article. <https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#gaining-consent-for-the-middle-tier-application>

Parameters

  • user_assertion (str) β€” The incoming token already received by this app
  • scopes (list[str]) β€” Scopes required by downstream API (a resource).
  • claims_challenge β€” The claims_challenge parameter requests specific claims requested by the resource provider in the form of a claims_challenge directive in the www-authenticate header to be returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. It is a string of a JSON object which contains lists of claims being requested from these locations.

Returns

A dict representing the json response from Microsoft Entra:

  • A successful response would contain "access_token" key, πŸ’–
  • an error response would contain "error" and usually "error_description".

πŸŽ€ remove_tokens_for_client()

remove_tokens_for_client()

Remove all tokens that were previously acquired via acquire_token_for_client() for the current client. Clean slate, fresh start, we love a reset. 🧹✨

πŸ¦‹ TokenCache πŸ¦‹

One of the parameters accepted by both PublicClientApplication and ConfidentialClientApplication is the TokenCache. πŸ‘‘ iykyk β€” that's the little vault doing all the work behind the scenes.

πŸ—‚οΈ class msal.TokenCache

okay bestie, this one is the base class πŸ’… she's giving "minimal but valid" β€” the absolute floor of cache behavior, nothing more.

The way that it holds tokens using a unified schema that's the same across literally every MSAL library? Iconic. Consistent. We love her 🫢

BUT. And this is a big but: this class does not serialize and does not persist them. Nope, nothing is going to disk here, no cap. If you want the whole save-it-forever situation, go look at the subclass SerializableTokenCache, she handles serialization.

add(event, now=None)

Handle a token obtaining event, and add tokens into cache. That's it, that's the job ✨

find(credential_type, target=None, query=None, *, now=None)

Literally equivalent to list(search(...)). Same thing, just wrapped in a list so you get the whole thing at once instead of a lazy generator.

search(credential_type, target=None, query=None, *, now=None)

Returns a generator of matching entries 🌸

Heads up bestie: it's O(1) for AT hits and O(n) for other types. Also lowkey important β€” it holds a lock during the entire search. The whole thing. Don't come for her about it.

You can subclass it to add new behavior, such as, token serialization. See SerializableTokenCache for example πŸ‘‘


πŸŽ€ class msal.SerializableTokenCache

This serialization can be a starting point to implement your own persistence, if that's the vibe you're going for.

Fr fr though: this class does NOT actually persist the cache on disk/db/etc. Nothing. It just gives you the string, you do the rest. Depending on your need, the following simple recipe for file-based, unencrypted persistence may be sufficient:
import os, atexit, msal
cache_filename = os.path.join(  # Persist cache into this file
    os.getenv(
        # Automatically wipe out the cache from Linux when user's ssh session ends.
        # See also https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/690
        "XDG_RUNTIME_DIR", ""),
    "my_cache.bin")
cache = msal.SerializableTokenCache()
if os.path.exists(cache_filename):
    cache.deserialize(open(cache_filename, "r").read())
atexit.register(lambda:
    open(cache_filename, "w").write(cache.serialize())
    # Hint: The following optional line persists only when state changed
    if cache.has_state_changed else None
    )
app = msal.ClientApplication(..., token_cache=cache)
...

Alternatively, you may use a more sophisticated cache persistence library, MSAL Extensions (https://github.com/AzureAD/microsoft-authentication-extensions-for-python), which provides token cache persistence with encryption, and more. Yes, encryption. Very much the glow-up version 🧚

Variables

  • has_state_changed (bool) β€” Indicates whether the cache state in the memory has changed since last serialize() or deserialize() call. She's your little "did anything move?" detector ✨

add(event, **kwargs)

Handle a token obtaining event, and add tokens into cache. Same energy as the parent version, but this one's got the serialization glow-up behind it.

deserialize(state: str | None) -> None

Deserialize the cache from a state previously obtained by serialize(). Basically: feed her back her own string and she'll rebuild the whole thing πŸ’–

serialize() -> str

Serialize the current cache state into a string. This is the string you hoard, hide, and feed back to deserialize() later πŸŽ€


πŸ’¬ Prompt

class msal.Prompt

This class defines the constant strings for prompt parameter. The values are based on https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest ⭐

The constants, all lined up and ready:

  • SELECT_ACCOUNT = 'select_account'
  • NONE = 'none'
  • CONSENT = 'consent'
  • LOGIN = 'login'

πŸ” PopAuthScheme

This is used as the auth_scheme parameter in many of the acquire token methods to support for Proof of Possession (PoP) tokens. Yes bestie, PoP tokens, she's fancy πŸ’…

New in MSAL Python 1.26 β€” so if you're on something older, girl, update 😭

class msal.PopAuthScheme(http_method=None, url=None, nonce=None)

The HTTP verb constants she keeps in her pocket:

  • HTTP_GET = 'GET'
  • HTTP_POST = 'POST'
  • HTTP_PUT = 'PUT'
  • HTTP_DELETE = 'DELETE'
  • HTTP_PATCH = 'PATCH'

__init__(http_method=None, url=None, nonce=None)

Create an auth scheme which is needed to obtain a Proof-of-Possession token 🫢

Parameters

  • http_method (str) β€” Its value is an uppercase http verb, such as "GET" and "POST". UPPERCASE, bestie, don't come in here with lowercase and act surprised πŸ‘€
  • url (str) β€” The url to be signed.
  • nonce (str) β€” The nonce came from resource's challenge.

πŸ’€ Exceptions

These are exceptions that MSAL Python may raise. You should not need to create them directly β€” like, pls don't, that's not your job, let MSAL handle it πŸ™ˆ You may want to catch them to provide a better error message to your end users. Your users will thank you. Your future self will thank you. Very main character of you, honestly.

class msal.IdTokenError(reason, now, claims)

In unlikely event of an ID token is malformed, this exception will be raised. Rare? Yes. Possible? Also yes. Now you know, so you're prepared πŸ’œ

πŸ¦‹ MANAGED IDENTITY πŸ¦‹

MSAL supports Managed Identity β€” like, the whole concept, here's the link if you wanna read the Azure gospel: https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview.

You can make one of these two kinds of managed identity configuration objects, bestie, pick your fighter:

  • class msal.SystemAssignedManagedIdentity β€” Represents a system-assigned managed identity. No thoughts, no IDs, just vibes.

It is equivalent to a Python dict of:

{"ManagedIdentityIdType": "SystemAssigned", "Id": None}

or a JSON blob of:

{"ManagedIdentityIdType": "SystemAssigned", "Id": null}
  • class msal.UserAssignedManagedIdentity(*, client_id=None, resource_id=None, object_id=None) β€” Represents a user-assigned managed identity. This one actually knows who it is.

Depends on the id you provided, the outcome is equivalent to one of the below:

{"ManagedIdentityIdType": "ClientId", "Id": "foo"}
{"ManagedIdentityIdType": "ResourceId", "Id": "foo"}
{"ManagedIdentityIdType": "ObjectId", "Id": "foo"}

And then you feed that configuration object straight into a ManagedIdentityClient object. That's the whole pipeline, periodt. πŸ’…

  • class msal.ManagedIdentityClient(managed_identity: dict | ManagedIdentity <#msal.managed_identity.ManagedIdentity> | SystemAssignedManagedIdentity <#msal.managed_identity.SystemAssignedManagedIdentity> | UserAssignedManagedIdentity <#msal.managed_identity.UserAssignedManagedIdentity>, *, http_client, token_cache=None, http_cache=None, client_capabilities: List[str] | None = None) β€” This API encapsulates multiple managed identity back-ends: VM, App Service, Azure Automation (Runbooks), Azure Function, Service Fabric, and Azure Arc. A whole buffet. πŸ“

It also provides token cache support.

Note: Cloud Shell support is NOT implemented in this class. Since MSAL Python 1.18 in May 2022, it has been implemented in PublicClientApplication.acquire_token_interactive() via calling pattern PublicClientApplication(...).acquire_token_interactive(scopes=[...], prompt="none"). That is appropriate, because Cloud Shell yields a token with delegated permissions for the end user who has signed in to the Azure Portal (like what a PublicClientApplication does), not a token with application permissions for an app.
  • __init__(managed_identity: dict | ManagedIdentity <#msal.managed_identity.ManagedIdentity> | SystemAssignedManagedIdentity <#msal.managed_identity.SystemAssignedManagedIdentity> | UserAssignedManagedIdentity <#msal.managed_identity.UserAssignedManagedIdentity>, *, http_client, token_cache=None, http_cache=None, client_capabilities: List[str] | None = None) β€” Create a managed identity client.

Parameters 🌸

  • managed_identity β€” It accepts an instance of SystemAssignedManagedIdentity or UserAssignedManagedIdentity. They are equivalent to a dict with a certain shape, which may be loaded from a JSON configuration file or an env var.
  • http_client β€”

An http client object. For example, you can use requests.Session(), optionally with exponential backoff behavior demonstrated in this recipe:

import msal, requests
from requests.adapters import HTTPAdapter, Retry
s = requests.Session()
retries = Retry(total=3, backoff_factor=0.1, status_forcelist=[
    429, 500, 501, 502, 503, 504])
s.mount('https://', HTTPAdapter(max_retries=retries))
managed_identity = ...
client = msal.ManagedIdentityClient(managed_identity, http_client=s)
  • token_cache β€” Optional. It accepts a msal.TokenCache instance to store tokens. It will use an in-memory token cache by default.
  • http_cache β€” Optional. It has the same characteristics as the msal.ClientApplication.http_cache.
  • client_capabilities (list[str]) β€”

(optional) Allows configuration of one or more client capabilities, e.g. ["CP1"].

Client capability is meant to inform the Microsoft identity platform (STS) what this client is capable for, so STS can decide to turn on certain features.

Implementation details: Client capability in Managed Identity is relayed as-is via xms_cc parameter on the wire.

Recipe 1: Hard code a managed identity for your app:

import msal, requests
client = msal.ManagedIdentityClient(
    msal.UserAssignedManagedIdentity(client_id="foo"),
    http_client=requests.Session(),
    )
token = client.acquire_token_for_client("resource")

Recipe 2: Write once, run everywhere. If you use different managed identity on different deployment, you may use an environment variable (such as MY_MANAGED_IDENTITY_CONFIG) to store a json blob like {"ManagedIdentityIdType": "ClientId", "Id": "foo"} or {"ManagedIdentityIdType": "SystemAssigned", "Id": null}. The following app can load managed identity configuration dynamically:

import json, os, msal, requests
config = os.getenv("MY_MANAGED_IDENTITY_CONFIG")
assert config, "An ENV VAR with value should exist"
client = msal.ManagedIdentityClient(
    json.loads(config),
    http_client=requests.Session(),
    )
token = client.acquire_token_for_client("resource")
  • acquire_token_for_client(*, resource: str, claims_challenge: str | None = None) β€” Acquire token for the managed identity. Go get that bag. ✨

The result will be automatically cached. Subsequent calls will automatically search from cache first. Lowkey efficient.

Parameters

  • resource β€” The resource for which the token is acquired.
  • claims_challenge β€”

Optional. It is a string representation of a JSON object (which contains lists of claims being requested).

The tenant admin may choose to revoke all Managed Identity tokens, and then a claims challenge will be returned by the target resource, as a claims_challenge directive in the www-authenticate header, even if the app developer did not opt in for the "CP1" client capability. Upon receiving a claims_challenge, MSAL will attempt to acquire a new token. Iconic of it honestly. πŸ‘‘

Note: Known issue: When an Azure VM has only one user-assigned managed identity, and your app specifies to use system-assigned managed identity, Azure VM may still return a token for your user-assigned identity. This is a service-side behavior that cannot be changed by this library. Azure VM docs: https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http

✍️ Author

Microsoft


2026, Microsoft


And that's a wrap on this whole page, bestie πŸŽ€ this was MSAL Python version 1.37.0, dated Jun 02, 2026, filed under MSALPYTHON(1) β€” go forth and auth responsibly, you absolute main character. πŸ’œπŸ¦‹

πŸ“¦ where this page comes from