Robot Framework and Python – authenticate to Microsoft Graph

In my last post, I’ve shown you how to authenticate to SharePoint using Robot Framework. Despite the broad SharePoint REST API capabilities, it is not enough to work with Office 365. Fortunately, we can access Graph API, which provides endpoints for users, groups, SharePoint, OneNote, Planner, Teams, etc. Let’s get authenticated and open the door to new possibilities!

Register AAD app

As with the SharePoint API, we have to register the app and assign it permission to access particular resources.

  1. Log in to portal.azure.com with your tenant credentials.
  2. Navigate to Azure Active Directory (AAD), App registrations and add a new app.
aad app registration
  1. Give the app a descriptive name i.e. “test-automation”. Leave redirect URI empty,
  2. You have created an app! Copy its id – “Application (client) id” and tenant id. You will need it later.
  3. Now, go to Authentication and mark “Default client type”. Don’t forget to save it.
default client type
  1. Go to the API permissions and add Microsoft Graph application permissions, if you want to use the application model, or delegated permissions, if you want to login with a user name and password.
  2. From the list, select only those permissions you need. Don’t pick all of them to avoid accidents and security breaches.
  3. Grant admin consent.
grant admin consent
  1. If you selected the application model, navigate to ?Certificates and secrets and create a new secret.
aad app registration secret
  1. Copy the generated client secret and store it safely. You won’t be able to recover it – only create a new one.

Let’s authenticate!

In point six I’ve added “Group.ReadWrite.All” delegated permission. I need this permission to read and delete groups. Check my next post to find out how to do it. But now, let’s focus on authentication.

First of all, install dependencies:
For AAD authentication: pip install adal
For making Graph requests: pip install requests

Application authorization

import adal
import time
class Graph:
    def __init__(self, tenantId, client_id, client_secret):       
        if not tenantId or not client_id or not client_secret:
            print('*ERROR:{0}* tenant id, client id or client secret is empty'.format(time.time()*1000))
            raise RuntimeError('*ERROR:{0}* tenant id, client id or client secret is empty'.format(time.time()*1000))
 
        context = adal.AuthenticationContext("https://login.microsoftonline.com/" + tenantId)
        self.token = context.acquire_token_with_client_credentials("https://graph.microsoft.com", client_id, client_secret)["accessToken"]

Note: I’ve used the log format which works well with Robot Framework.

Delegated authorization

def __init__(self, tenantId, client_id, username, password):
    if not tenantId or not client_id or not username or not password:
        print('*ERROR:{0}* tenant id, client id, username or password is empty'.format(time.time()*1000))
        raise RuntimeError('*ERROR:{0}* tenant id, client id, username or password is empty'.format(time.time()*1000))
    context = adal.AuthenticationContext("https://login.microsoftonline.com/" + tenantId)
# Delegated authorization
    self.token = context.acquire_token_with_username_password("https://graph.microsoft.com", username, password, client_id)["accessToken"]

The code is pretty much the same. The difference is that we use the user name and password to get access token instead of the client secret.

Test request

We have an access token. Let’s create a simple GET request.

    def get_all_groups(self, queryParameter=''):
        return self._graph_get_call('/groups{0}'.format(queryParameter))
 
    BASE_URL_V1 = 'https://graph.microsoft.com/v1.0{0}'
 
    def _graph_get_call(self, url):
        request_url = self.BASE_URL_V1.format(url)
        response = requests.get(url=request_url, headers=self._get_default_headers())
        return response.json()
 
    def _get_default_headers(self):
        return {
            'Authorization': 'Bearer {0}'.format(self.token),
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
 
graph = Graph('{tenantId}', '{clientId}', '{clientSecret}')
group = graph.get_all_groups('?$top=1')

I’ve used “/groups” endpoint, which returns all available groups in the tenant.

Note: Actually, this call returns up to two hundred groups by default. It’s because of the performance reasons

I’ve scattered it into three methods:

  • _get_default_headers – returns a header with content type, accept type and most important the access token preceded by the word ‘Bearer’.
  • _graph_get_call – executes GET request to the endpoint provided as an argument. Adds endpoint URL to the base API URL, attaches authorization headers, and returns JSON parsed dictionary.
  • get_all_groups – calls _graph_get_call with ‘/groups’ endpoint. It adds query string at the end.

Summary

Accessing Graph API from a Python script requires a few steps of configuration. I went through them and I hope this post will make it easier for you. Check my next post to find out how to use this class in Robot Framework and how to implement other methods.