Microsoft Authentication Library (MSAL) for Python

repository·dev·Indexed 21 days ago

https://github.com/azuread/microsoft-authentication-library-for-python

A library that enables Python applications to integrate with the Microsoft identity platform to sign in users or apps using Entra ID and Microsoft Accounts. It provides tools for token acquisition and caching via PublicClientApplication and ConfidentialClientApplication, supports Azure Managed Identities, and offers migration paths from ADAL.

Tokens
6.5K
Snippets
17
Records
28
Agent score
76%

What's inside MSAL Python

  1. Identify your application scenario for MSAL Python

    dev

    MSAL Python supports different authentication flows based on your application type. Use the following mental model to choose your implementation:

    User-Facing Applications

    These scenarios acquire tokens representing a signed-in user.

    • Web Apps: Standard web applications.
    • Desktop Apps: Interactive applications running on a user's machine.
    • Browserless Apps: Applications that use flows like Device Code Flow (e.g., CLI tools).

    Daemon Applications

    • Daemon Apps: These acquire tokens representing the application itself (service principal), not a specific user. These are typically implemented using ConfidentialClientApplication.
  2. Understand the MSI v2 In-Memory Key Approach

    dev

    The MSI v2 In-Memory Key approach implements a Managed Service Identity (MSI) path using an in-memory software RSA key instead of a hardware-backed KeyGuard.

    Key Characteristics:

    • Exportable Keys: The private key is exportable, allowing standard Python HTTP libraries like requests to be used for both token acquisition and resource calls without specialized MSAL helpers.
    • Cross-Platform: Unlike the KeyGuard path which is Windows-only, the in-memory approach is cross-platform.
    • Security Trade-off: It provides lower security than KeyGuard because it does not use attestation (MAA) to prove hardware backing, but it is significantly simpler to implement and use.
    • API Behavior: When calling acquire_token_for_client with mtls_proof_of_possession=True, the resulting dictionary includes both cert_pem and key_pem.
  3. Understand the difference between Public and Confidential Client Applications

    dev

    MSAL Python enforces a clean separation between two types of client applications as defined in OAuth 2.0 standards:

    • PublicClientApplication: Used for applications that cannot keep a client secret confidential (e.g., desktop apps, mobile apps, or browserless CLI tools).
    • ConfidentialClientApplication: Used for applications capable of maintaining a client secret (e.g., web apps or daemon services running on a secure server).

    These are implemented as separate classes with different methods tailored to their specific authentication scenarios.

  4. How MSAL Python token acquisition works

    dev

    Acquiring tokens with MSAL Python follows a high-level 3-step pattern designed to optimize performance and user experience through token caching:

    1. Initialize the Application: Create an instance of either PublicClientApplication (for apps that cannot keep a secret, like desktop/mobile apps) or ConfidentialClientApplication (for apps that can securely store a secret, like web apps). You should reuse this instance throughout the application lifecycle.
    2. Check the Cache: Use the token cache to see if there are existing accounts or valid tokens. This allows you to perform silent authentication and automatically handles token refreshes.
    3. Acquire New Token: If no suitable token is found in the cache, call the appropriate acquisition method (e.g., acquire_token_by_authorization_code, acquire_token_by_client_credential, etc.) to request a new token from the Microsoft identity platform.
    from msal import PublicClientApplication
    
    # 1. Initialize
    app = PublicClientApplication(
        "your_client_id",
        authority="https://login.microsoftonline.com/Enter_the_Tenant_Name_Here"
    )
    
    result = None
    
    # 2. Check Cache
    accounts = app.get_accounts()
    if accounts:
        chosen = accounts[0]
        result = app.acquire_token_silent(["your_scope"], account=chosen)
    
    # 3. Acquire New Token if needed
    if not result:
        result = app.acquire_token_by_one_of_the_actual_method(..., scopes=["User.Read"])
    
    if "access_token" in result:
        print(result["access_token"])
    else:
        print(result.get("error"))
  5. Understand the MSAL Python version support policy

    dev

    MSAL Python follows a support policy aligned with the Azure SDK for Python. It supports Python versions while they are supported by the Python Software Foundation (PSF), plus a 6-month grace window after the PSF end-of-support date.

    Key Policy Details:

    • New Releases: Once a Python version reaches its MSAL Python End of Support date, new MSAL Python releases will no longer install on, be tested against, or accept bug fixes for that version.
    • Breaking Changes: Dropping support for a Python version is treated as a breaking change and is delivered via a new minor or major release (never a patch).
    • Existing Applications: Older MSAL Python releases that supported your Python version remain installable via pip due to requires-python resolution. However, these older releases will not receive new features or security fixes.
    • Implementation: Support status is managed via setup.cfg (using python_requires and trove classifiers), CI/CD test matrices, and dependency tracking.
  6. Compare KeyGuard vs In-Memory MSI v2 paths

    dev

    When choosing an MSI v2 implementation path, consider the following differences:

    AspectKeyGuardIn-Memory
    Key typeNon-exportable CNG/VBSExportable software RSA
    AttestationMAA (proves hardware)None
    key_pem in result?❌ Impossible✅ Yes
    Token acquisitionWinHTTP/SChannel (ctypes)requests + cert/key PEM
    Resource callmtls_http_request() helperStandard requests
    Helper needed?YesNo
    PlatformWindows onlyAny (cross-platform)
    Security★★★★★★★☆☆☆
  7. Use TokenCache for token storage and serialization

    dev

    Both PublicClientApplication and ConfidentialClientApplication accept a TokenCache object as a parameter. The TokenCache manages the storage and retrieval of acquired tokens.

    To implement custom behavior, such as saving tokens to a file or database, you can subclass TokenCache. A common pattern is using SerializableTokenCache to handle token serialization.

  8. How Managed Identity v2 (MSI v2) works

    dev

    Managed Identity v2 (MSI v2) improves security over MSI v1 by using mTLS Proof-of-Possession (PoP) tokens.

    The Workflow:

    1. Key Generation: The client generates a non-exportable RSA key using Windows NCrypt/KeyGuard.
    2. CSR Creation: A PKCS#10 Certificate Signing Request (CSR) is built.
    3. Attestation (Optional): If enabled, the client collects attestation evidence via Azure Attestation (MAA).
    4. Credential Issuance: The client sends the CSR (and optional attestation) to the Azure IMDS /issuecredential endpoint.
    5. Certificate Retrieval: IMDS returns a client certificate and the Entra STS URL.
    6. Token Exchange: The client performs an mTLS request to Entra STS using the certificate to request a token with token_type=mtls_pop.
    7. Verification: The client verifies that the token's cnf.x5t#S256 claim matches the certificate's SHA-256 thumbprint to ensure the token is bound to the specific certificate.
  9. Perform mTLS resource calls with In-Memory keys

    dev

    Because the in-memory path provides an exportable key_pem, you do not need a specialized MSAL helper to call protected resources. You can use the standard requests library. Since requests requires file paths for certificates, you must write the PEM strings to temporary files before making the call.

    Note: Ensure you clean up the temporary files after the request is complete.

    import requests
    import tempfile, os
    
    # Assuming 'result' was obtained from acquire_token_for_client
    cert_pem = result['cert_pem']
    key_pem = result['key_pem']
    access_token = result['access_token']
    token_type = result['token_type']
    
    # Write cert + key to temp files (requests needs file paths)
    with tempfile.NamedTemporaryFile(mode='w', suffix='.pem', delete=False) as cf:
        cf.write(cert_pem)
        cert_path = cf.name
    with tempfile.NamedTemporaryFile(mode='w', suffix='.pem', delete=False) as kf:
        cf.write(key_pem)
        key_path = kf.name
    
    try:
        resp = requests.get(
            "https://tokenbinding.vault.azure.net/secrets/boundsecret/?api-version=2015-06-01",
            cert=(cert_path, key_path),
            headers={
                "Authorization": f"{token_type} {access_token}",
                "x-ms-tokenboundauth": "true",
            },
        )
    finally:
        os.unlink(cert_path)
        os.unlink(key_path)
  10. Perform mTLS resource calls with `mtls_http_request()`

    dev

    When using MSAL Python's MSI v2 flow, you acquire an mtls_pop token bound to a KeyGuard-protected certificate. Because KeyGuard keys are non-exportable, standard Python HTTP libraries like requests, httpx, or urllib3 (which use OpenSSL) cannot access the private key to perform mTLS.

    You must use the mtls_http_request() helper. This function uses WinHTTP/SChannel (the Windows-native TLS stack) via ctypes, allowing it to access non-exportable CNG keys natively.

    Requirements for successful mTLS resource calls:

    1. Header: You must include the x-ms-tokenboundauth: true header (required by services like Azure Key Vault) to trigger the server to request the client certificate.
    2. Protocol: Use HTTP/1.1. TLS renegotiation (required for client certificate requests) is forbidden in HTTP/2.
    3. TLS Version: Use TLS 1.2. TLS 1.3 uses post-handshake authentication which may not be fully supported by WinHTTP in this context.
    from msal.msi_v2 import mtls_http_request
    import base64
    
    # 1. Acquire the mtls_pop token
    result = client.acquire_token_for_client(
        resource="https://vault.azure.net",
        mtls_proof_of_possession=True,
        with_attestation_support=True,
    )
    
    # 2. Prepare the certificate from the auth result
    cert_der = base64.b64decode(result["cert_der_b64"])
    
    # 3. Make the mTLS request using the helper
    resp = mtls_http_request(
        "GET",
        "https://tokenbinding.vault.azure.net/secrets/boundsecret/?api-version=2015-06-01",
        cert_der,
        headers={
            "Authorization": f"{result['token_type']} {result['access_token']}",
            "Accept": "application/json",
            "x-ms-tokenboundauth": "true",
        },
    )