This blog shows how to use MSAL for Python to perform an interactive sign in to Azure AD from running a local python script. The sample also demonstrates how to enable MSAL logging along with how to capture Python SSL web traffic using Fiddler Classic

App Registration:

You will need to have an Azure AD App Registration with “http://localhost” reply URL configured in the ‘Mobile and desktop applications’ platform

The Code:

If you don’t have msal and requests module already installed you may need to run both “pip install msal” and “pip install requests” commands to get these installed first.

Logging:

logging is enabled with the following code to set logging level to DEBUG

logging.basicConfig(level=logging.DEBUG)  # Enable DEBUG log for entire script
logging.getLogger("msal").setLevel(logging.DEBUG)  # Optionally disable MSAL DEBUG logs

Fiddler HTTPS capture

Capturing HTTPS traffic from Python application using Fiddler can be very challenging. The application uses MSAL library to authenticate with Azure AD and then uses the requests module to send request to MS Graph endpoint. The following code is used to help with Fiddler capture (assuming Fiddler is listening on port 8888)

app = msal.PublicClientApplication(client_id = appId, authority = "https://login.microsoftonline.com/"+ tenantId,verify = False)
graph_response = requests.get(  # Use token to call downstream service
        graphurl,
        headers={'Authorization': 'Bearer ' + result['access_token']},
        proxies={"http": "http://127.0.0.1:8888","https":"http://127.0.0.1:8888"},verify=False)

Below is the complete python script you can run locally on the machine. Make sure to provide your tenant ID and Application ID

import sys
import json, logging, msal, requests

# comment out the following 2 lines if you dont't want to enable MSAL logging
logging.basicConfig(level=logging.DEBUG)  # Enable DEBUG log for entire script
logging.getLogger("msal").setLevel(logging.DEBUG)  # Optionally disable MSAL DEBUG logs

tenantId = "<Tenant ID>"
appId = "<Application ID>"
scope = ["User.Read"]
graphurl = "https://graph.microsoft.com/v1.0/me"

app = msal.PublicClientApplication(client_id = appId, authority = "https://login.microsoftonline.com/"+ tenantId)

# use the following instead for Fiddler capture
# the verify = False flag is to tell the program to ignore SSL certificate verification.  We will need this since we are using Fiddler SSL certificate for HTTPS capture
# app = msal.PublicClientApplication(client_id = appId, authority = "https://login.microsoftonline.com/"+ tenantId,verify = False)

result = None

# accounts = app.get_accounts(username = "bhadmin@bachoang99live.onmicrosoft.com")
accounts = app.get_accounts()
if accounts:
    logging.info("Account(s) exists in cache, probably with token too. Let's try.")
    print("Account(s) already signed in:")
    for a in accounts:
        print(a["username"])
    chosen = accounts[0]  # Assuming the end user chose this one to proceed
    print("Proceed with account: %s" % chosen["username"])
    # Now let's try to find a token in cache for this account
    result = app.acquire_token_silent(scope, account=chosen)

if not result:
    logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
    print("A local browser window will be open for you to sign in. CTRL+C to cancel.")
    result = app.acquire_token_interactive(  # Only works if your app is registered with redirect_uri as http://localhost
        scopes = scope,
        #parent_window_handle=...,  # If broker is enabled, you will be guided to provide a window handle
        # login_hint=config.get("username"),  # Optional.
            # If you know the username ahead of time, this parameter can pre-fill
            # the username (or email address) field of the sign-in page for the user,
            # Often, apps use this parameter during reauthentication,
            # after already extracting the username from an earlier sign-in
            # by using the preferred_username claim from returned id_token_claims.

        # prompt=msal.Prompt.SELECT_ACCOUNT,  # Or simply "select_account". Optional. It forces to show account selector page
        #prompt=msal.Prompt.CREATE,  # Or simply "create". Optional. It brings user to a self-service sign-up flow.
            # Prerequisite: https://docs.microsoft.com/en-us/azure/active-directory/external-identities/self-service-sign-up-user-flow
        )

if "access_token" in result:
    # Calling graph using the access token

    # use the following code if you want to capture SSL traffic in Fiddler
    # graph_response = requests.get(  # Use token to call downstream service
    #    graphurl,
    #    headers={'Authorization': 'Bearer ' + result['access_token']},
    #    proxies={"http": # "http://127.0.0.1:8888","https":"http://127.0.0.1:8888"},verify=False)

    graph_response = requests.get(  # Use token to call downstream service
        graphurl,
        headers={'Authorization': 'Bearer ' + result['access_token']})
    print("Graph API call result: %s ..." % graph_response.text[:100])
else:
    print(result.get("error"))
    print(result.get("error_description"))
    print(result.get("correlation_id"))  # You may need this when reporting a bug 

References:

msal.application.ClientApplication class | Microsoft Learn

microsoft-authentication-library-for-python/interactive_sample.py at dev · AzureAD/microsoft-authentication-library-for-python · GitHub

Leave a Comment