Fabian G. Williams aka Fabs

Fabian G. Williams

Sr. Program Manager, Microsoft @fabianwilliams.

Microsoft Graph and Python

Using Microsoft Graph and Python to access Microsoft 365 Data and Services

Fabian Williams

5-Minute Read

Python Plus Microsoft Graph

Why you should read this post

If you are interested in Microsoft Graph and how it can work with Python this post gives two examples of an art of the possible. In this post i will be addressing the following topics:

  • Using MSAL libraries in Python
  • Using 2 different Authentication Flows to gain access to Microsoft Graph resources
  • Bonus - Access to my GitHub repo where you can get the code as well as a Jupyter Notebook to runs this on your own

Wiring this all up

This is not very onerous, in fact if you have ever played with Python, its the simplicity that is really astounding. Basically you need to work the the below libraries to get this sorted:

  1. Microsoft Authentication Library - used to do the authentication to Azure Active Directory which is needed for getting the token needed for Microsoft Graph
  2. Requests - needed to send your calls to the URI endpoints
  3. JWT (Optional) - for token display and handling demo purposes only
  4. Pandas (Optional) - used for displaying my payload returned from the graph in a Dataframe (Think excel rows)
import msal
import jwt
import json 
import requests 
import pandas as pd 
from datetime import datetime
from configparser import ConfigParser

config = ConfigParser()
config.read('config.ini')

I also employ json, datetime, and configparser for utilities within my code and I will explain why as I go along. I will be showing 2 methods of approach here as mentioned. App Permission to retrieve a list of users from the Graph as well as Delegated Permission using Device Flow authentication to do the same.

Using Application Permission

The things to pay close attention to in the below code snippet is teh msal.ConfidentialApplication. Thats why you should always use the Graph SDK, this one line of code in additonan to the “accessToken” acquiring th token under the “try” block is responsible for all the magic. Specifically, I am using Client Credential Provider Auth Flow and supplying it the identification of the App using the Client ID and Secret. Want more details on Auth Flow click here please

def msgraph_auth():
    global accessToken
    global requestHeaders
    global tokenExpiry
    tenantID = config['apppermissiononly'] ['tenantID']
    authority = config['apppermissiononly'] ['authority'] + tenantID
    clientID = config['apppermissiononly'] ['clientID']
    clientSecret = config['apppermissiononly'] ['clientSecret']
    scope = ['https://graph.microsoft.com/.default']

    app = msal.ConfidentialClientApplication(clientID, authority=authority, client_credential = clientSecret)

    try:
        accessToken = app.acquire_token_silent(scope, account=None)
        if not accessToken:
            try:
                accessToken = app.acquire_token_for_client(scopes=scope)
                if accessToken['access_token']:
                    print('New access token retreived....')
                    requestHeaders = {'Authorization': 'Bearer ' + accessToken['access_token']}
                else:
                    print('Error Caught: Check the Config File to make sure tenantID, clientID and clientSecret is correct')
            except:
                pass 
        else:
            print('Token retreived from MSAL Cache....')
        return
    except Exception as err:
        print(err)

def msgraph_request(resource,requestHeaders):
    # Request
    results = requests.get(resource, headers=requestHeaders).json()
    return results

Next its a matter of calling those functions, one for the Auth, and the ther to get the query results.

# Call the Authenticate against Graph Function
msgraph_auth()

#going after the V1 endpoint for users
queryResults = msgraph_request(graphURI +'/v1.0/users',requestHeaders)

# Send the results to a Pandas Dataframe
try:
    df = pd.read_json(json.dumps(queryResults['value']))
    # set ID column as index in Pandas Dataframe
    df = df.set_index('id')
    # print the entire datafarame from Pandas
    print(str(df))
    #print(str(df['displayName'] + " " + df['mail']))

except:
    print(json.dumps(queryResults, indent=2))

Watch it run

One you invoke the python executable you get the token and Pandas gives you a beautiful dataframe to see the results. Query Results Returning Values from Microsoft Graph

Using Delegated (Interactive) Permission

So, in delegated, we are not running unattended, we do need to authentiate. In this scenario we are going to use “Device Code” Auth Flow. This means that when we need to get the token, AAD will challenge us to enter a code at a certain URL, we will need to authenticate using our credentials and consent to the Application and scope it is asking for. We’re also using the PublicClientApplication here using just the Client ID, everything else for this Auth, we will explicitly consent for. See below…

app = msal.PublicClientApplication(clientID, authority=authority)

flow = app.initiate_device_flow(scopes=SCOPES)
if 'user_code' not in flow:
    raise Exception('Failed to create device flow')

print(flow['message'])

then we pretty much make the same calls as before except for our token we are asking for it via device flow andd we inspect the acccess token in our callback and go to town getting our results.

result = app.acquire_token_by_device_flow(flow)

if 'access_token' in result:
    result = requests.get(f'{ENDPOINT}/users', headers={'Authorization': 'Bearer ' + result['access_token']}).json()
    #Going after a specific user Diego S
    #result = requests.get(f'{ENDPOINT}/users/fa2480cc-103b-4b53-8ba6-da9720c81c2d/presence', headers={'Authorization': 'Bearer ' + result['access_token']})
    #result.raise_for_status()
    #print(result.json())
    df = pd.read_json(json.dumps(result['value']))
    # set ID column as index in Pandas Dataframe
    df = df.set_index('id')
    # print the entire datafarame from Pandas
    print(str(df))
    #print(str(df['displayName'] + " " + df['mail']))

else:
    raise Exception('no access token in result')

Summary

This code as well as the Jupyter Notebook which allows you to run this all on your own in your own envionment may be found in this GitHub Repo. I do hope you enjoyed this little post on how to get started using Python with Microsoft Graph. Oh, one more thing 💡 you may have noticed me using config file variable when I was assigning Client ID, Tenant ID and the lot, that file is not in the repo for obvious reasons but an example of how that file is constructed is below for reference:

[apppermissiononly]
tenantID = redacted-1e7e2f9de3e2
authority = https://login.microsoftonline.com/
clientID = redacted-654fbbfaa793
clientSecret = totally_redacted_rubbish

[delegatedpermissiononly]
tenantID = rubbish-1e7e2f9de3e2
authority = https://login.microsoftonline.com/
clientID = more-rubbbish-215b06

I would suggest however that you apply a more secure way of storing your configurables and credentials by using something like Azure Key Vault. This is demoware mind you 😜

Chat about this?

Engage with me Click
Twitter @fabianwilliams
LinkedIn Fabian G. Williams

Or use the share buttons at the top of the page! Thanks

Cheers! Fabs

Recent Posts

Categories

About

Fabian G. Williams aka Fabs Site