Microsoft Graph using MSAL with Python and Delegated Permissions

Recently I wrote the Microsoft Graph using MSAL with Python post and mentioned that I would be writing up Python versions of similar MSAL posts I have written using PowerShell. Specifically, MSAL using certificate-based authentication and the topic for this post MSAL with Python and Delegated Permissions.

In this post I show how to authenticate and query Microsoft Graph using MSAL with Python after obtaining an access and refresh token using a Device Code flow and then refreshing the tokens using the MSAL Python package.

Update 15 April 2021:
See this post for Microsoft Graph using MSAL
with Python and Certificate Authentication

Since I wrote the previous post the MSAL Python package has been updated and the current release is 1.10.0. I’ve used v 1.10.0 when writing this post.

Prerequisites

This post assumes you have Python installed and configured as well as PIP on your local host. Ideally you should also be using VSCode along with the Microsoft Python extension for VSCode.
You will also need to have registered an Azure AD Application that contains Delegated Permissions.

Your Azure AD Registered Application must also be enabled for Public Client Flows.

Dependencies

Just as PowerShell uses Modules to provide functionality Python uses Packages. The packages I am using for integration with Microsoft Graph are.

  • MSAL (simplifies authentication and access token refresh with Microsoft Graph)
  • MSAL_Extensions (required to utilize the MSAL persistent cache)
  • PyJWT (we will be using this to decode the Microsoft Graph Access Token)
  • 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 Device Code 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 configured in the script has an entry in the MSAL cache and we can request a new access token for that account for use with the registered application. If it is the first-time the script has been run, the interactive authentication process using the Device Code flow is required.
  • 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_device_flow
    • this function is the interactive authentication process using the Device Code flow.
      • it is only required on the first logon on a host with no user entry in the MSAL Cache.
  • 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

You will need to update the following variables near the top of the script below.

  • your tenantID (tenant GUID) or Tenant Name (mytenant.onmicrosoft.com), of your registered Azure AD Application
  • clientID of your registered Azure AD Application
  • scope for your scope(s) associated with your registered Azure AD Application
  • username that you will be using to authenticate to Azure AD and use delegated permissions as for Microsoft Graph calls.

Example Flow – MSAL with Python and Delegated Permissions

Executing the script for the first time will not find the MSAL cache with the configured user account (configured in the username variable), so will trigger a Device Code flow. You will be prompted to open a browser and go to https://microsoft.com/devicelogin and enter the code provided in the console output.

In the browser after navigating to https://microsoft.com/devicelogin the following window will appear to past the code into.

Login with an account that you will use to access the registered application and make Microsoft Graph API calls.

If you have configured the Azure AD Application correctly (for public flows and scopes with delegated permissions) you will receive confirmation of successful authentication and the script will have received an access and refresh token.

After authenticating the script will make a Graph API call to return the users Azure AD Account.

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 default Access Token lifetime is 60 minutes. We can see below that even though the current access token would be valid for another ~38 minutes we forced a refresh.

The Script

Here is the script. Do not forget to install the packages and update the tenantID, clientID, scope(s) and username before executing it.

Summary

In this post I have shown how to configure an Azure AD Application for Delegated Permissions and how to leverage it using Python and the Microsoft Authentication Libraries (MSAL).

The example script will allow you to authenticate to an Azure AD Application 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.