Earlier this year I wrote this post on Microsoft Graph using MSAL with Python and Delegated Permissions. That post used the Device Code flow which was valid for the particular scenario I had at that time. This post whilst also using delegated permissions and MSAL with Python uses Interactive Flow. Interactive Authentication to Microsoft Graph using MSAL with Python and Delegated Permissions means that the first request will open a browser window and trigger Azure Active Directory authentication. Subsequent authentication events will utilise the local MSAL cache and be performed silently.
Dependencies
The dependencies are the same as for my Device Code flow example as the primary difference is just the authentication flow. The packages I am using for integration with Microsoft Graph are.
- MSAL (simplifies authentication and access token refresh with Microsoft Graph)
- the most recent version at the time of this post is 1.13.0
- MSAL_Extensions (required to utilize the MSAL persistent cache)
- the most recent version at the time of this post is 0.3.0
- PyJWT (we will be using this to decode the Microsoft Graph Access Token)
- You will need v1.7.1 of PyJWT. Version 2.x + of PyJWT has breaking changes for jwt.decode.
- UPDATE June 2022: See this post to utilise PyJWT version 2.4.0+
- JSON (for manipulation of the results from Microsoft Graph queries)
- REQUESTS (for REST requests to Microsoft Graph)
- DATETIME (to convert access token expiry from a Unix timestamp)
Python includes some of these packages. Install them using PIP.
Note: In the screenshot below, I already have all the packages installed.
pip install msal msal_extensions pyjwt==1.7.1 requests datetime
MSAL with Python and Delegated Permissions Overview
Using Delegated Authentication, the first time you authenticate to Azure AD on a new host the authentication process is interactive. That is, you must authorize your identity with the registered Azure AD Application.
The common method to do that is to use the Interactive Flow. This is only required for the first login. Subsequent logins will leverage the MSAL cache which will contain your account details along with your most recent access token and refresh token. Subsequently we can request a new access token using the stored refresh token from the MSAL cache and that can be done silently.
The MSAL package by default when asked to acquire a new access token silently will only do so if the current access token is about to expire or has expired. When can however force a refresh to obtain a new access token even if the one we have is still valid.
To simplify these scenarios, I have created a series of functions. The script contains seven functions.
Script Functions
- msal_persistence
- this function uses the msal_extensions package and ensures that the MSAL cache is persistent between sessions.
- the MSAL cache is securely created and written locally.
- a file named ‘token_cache.bin‘ will be created if it does not already exist in the directory the script is located.
- msal_cache_accounts
- this function, after loading the MSAL cache from disk to local memory returns the user accounts contained in the cache
- we can use this to determine if the user account prompted for in the script has an entry in the MSAL cache and we can request a new access token for that account. If it is the first-time the script has been run, the interactive authentication process is required.
- this function, after loading the MSAL cache from disk to local memory returns the user accounts contained in the cache
- msal_delegated_refresh
- This function will refresh an access token for a user using the refresh token from the MSAL cache.
- msal_delegated_refresh_force
- like msal_delegated_refresh but forcing the refresh of the access token even though it may not be close to expiring.
- msal_delegated_interactive_flow
- this function is the interactive authentication process using the interactive flow.
- it is only required on the first logon on a host with no user entry in the MSAL Cache.
- this function is the interactive authentication process using the interactive flow.
- msal_jwt_expiry
- this function looks at the access token and displays relative to the hosts time zone the expiry time of the access token.
- msgraph_request
- a function to perform GET requests from Microsoft Graph.
MSAL with Python and Delegated Permissions Script Configuration
Unlike the Device Code Flow example, this one leverages the Azure PowerShell well-known Client ID and will prompt for your Azure AD user account name. It then queries and returns which Azure Active Directory tenants the AAD user account is federated too. Effectively a Python script version of my PowerShell Module detailed in this post here.
Example Flow – Interactive Authentication to Microsoft Graph using MSAL with Python and Delegated Permissions
Executing the script for the first time will not find the MSAL cache with the username obtained from the console prompt so will trigger an interactive authentication flow. A browser window will open directed at the tenant for the AAD Account username. If your browser is logged into Azure AD then no other interaction is required.
Using the example script and the well-known Client ID for Azure PowerShell only authentication is required. If you are using your own Client ID for an Azure AD Registered Application you will be prompted for consent the very first time. In both cases though the username and domain name are passed through meaning you only need to provide the associated account password.
Following successful authentication a response similar to the following will appear in the browser and the remainder of the script will run executing the query.
Re-running the Script
Re-running the script uses the tokens already retrieved and stored in the MSAL cache. Note the same token expiry time from the previous execution of the script above. Even though the msal_delegated_refresh function was called, because the token had plenty of time remaining it wasn’t refreshed.
Forcing an Access Token Refresh
Using the msal_delegated_refresh_force function we can force a refresh of our tokens. We pass the function the clientID, scope(s), authority and MSAL Account.
# Force Token Refresh result = msal_delegated_refresh_force(clientID, scope, authority, myAccount)
The Script
Here is the script. Do not forget to install the python package dependencies. To take this example script and use it for other Microsoft Graph queries change the Client ID on line 10 for your Azure AD Registered application, the scope(s) on line 11 and your query on lines 110 and 123 .
Summary
In this post I have shown how to use a well-known Azure AD Client ID with Delegated Permissions and how to leverage it using Python and the Microsoft Authentication Libraries (MSAL) via an interactive authentication flow.
The example script will allow you to authenticate the first time, then use cached credentials securely stored locally to refresh your access token. If required you can also force the refresh of your access token.