The Microsoft Graph-API is the gateway to almost all data living within a Microsoft365 subscription, and is therefore a super powerful API for a lot of different use cases.
But approaching and using it for the first time can be very difficult and overwhelming – especially because the official documentation is very complex and information is shattered across a lot of different pages.
I just had to create a serverless function in Node.js that retrieves some data from a SharePoint-List – and while trying to find out how to do that, I got lost clicking from page to page, concept to concept and code example to code example to find a quick and easy solution I can built upon.
The goal of this post: A pracitcal guide
The main problem I had was: How to correctly authenticate and authorize my backend-app with a simple secret to use the Graph-API to query general (non-user related) data?
With this post I want to save you the trouble I went through and show you exactly this in a practical guide. I will only explain enough theory for you to understand what is going on.
The guide explains how to set everything up to query the SharePoint root site as an example, but it also explains how to adjust everything to match your desired data.
The code examples are written in TypeScript and run in Node.js. But as we will be using the official Azure auth library @azure/msal-node which is available in other languages as well (you can find a list here), the concepts should still be applicable if you fancy another language.
I am also planning to write a second part to this post that takes a deeper dive into the concepts behind the authentication and authorization models of Microsoft365 and that will explain some leftover questions from this first part – so stay tuned.
But for now, let's get started.
Prerequisites – What you need to know first
Azure AD Application required
In order to authenticate with the Graph-API, you must register an app in the Azure Active Directory (Azure AD for short). This is the way of creating a technical user for the API, and there really is no way around this.
In hindsight, this was one of the changes in my mental model I had to go through to understand the auth-mechanism of Microsoft365: Rather than creating a (somewhat anonymous) technical user to use the API, you actually register the app itself as identity within your account.
For the use-case of this post, there is no real technical difference between the two. However, Microsoft allows for far more complex authentication scenarios where this becomes very relevant. I'll talk about those in the second part.
Administrator Privileges required
You will need administrator privileges to register the app. If you don't have them, there are three other options:
- You ask your IT-Department or whoever is in charge to register the app for you.
- You use the App registrations page in Azure if you have access. This is the admin-free version of registering an app and you can then follow all of the steps below – however, you won't be able to give the consent required in step 1.3. and then still need an administrator to consent for you.
- You create a completely free Microsoft365 Developer test account here – which gives you a full Microsoft365 E5 Subscription for a renewable 90 days. This is perfect as a playground and what I did to test the code of this post.
Practical Guide
Now lets dive into the practical part which consists of three parts:
- 1. Registering an Azure AD Application
- 2. Using TypeScript to query the API
- 3. Using the Graph-Explorer to find the right query
1. Registering an Azure AD Application
If you have administrator privileges, you can reach the Azure AD App registrations page by opening your browser and navigating to https://aad.portal.azure.com. This should bring you to the Azure Active Directory admin center of your Microsoft365 or Azure Subscription once you logged in with your administrator account.
Alternatively, you can reach it from either within your Microsoft365 admin center in the navigation on the left, or from the Azure portal by searching for it in the top bar.
Whatever way you chose, once you are there, on the left side click Azure Active Directory and then App registrations.
Next, we'll have to do 3 things: register the app, then create a client secret and in the end add some permissions to it – which I'll explain step by step.
Don't worry of making any mistakes here – you can always adjust the settings later and there is absolutely no risk of creating any costs as registering an app is completely free.
1.1. Registering the Application
Fill the Form with a name of your choosing and select "Accounts in this organizational directory only" (you can omit the Redirect URI), then hit
Register
.
You should see a summary screen of your app from which you will later need the values of the Application (client) id and the Directory (tenant) id. You can copy and store them now or retrieve them again later.
Think of those variables like this:
- tenant-id: The unique Id of the entire Microsoft365- or Azure-Account.
- client-id: The username of the technical user our application will use to authenticate.
Now all that is missing for a proper authentication is a password or client-secret
– we will create this in the next step.
1.2. Authentication: Create a client-secret
On the left, select
Certificates & secrets
and clickNew Client secret
.
Give it any name and choose an expiry date (you can just use
Never
for now).
You should see the created secret and the value in clear text. Copy and store this value somewhere now – this will be the only time you can see it.
One note: Don't confuse the ID
field of this screen with the client-id of the previous step – they are not the same and you won't need the client-secret-id from here anymore.
Now we have everything we need to let our application authenticate against Azure AD. However, we didn't tell Azure-AD what data our application is allowed or authorized to retrieve yet, so lets do this next.
1.3. Authorization: Grant permissions to your app
Still on the Azure AD Application page:
Search and select Sites.ReadAll. This is the permission that allows us to read all SharePoint-Data. If you need to query other data and therefore need other permissions, you can later use the Graph-Explorer as explained in Step 3.
Click Add Permissions.
Back on the permissions-screen, click Grant admin consent to actually allow those permissions. I'll explain in the second part why this is necessary, for now just do it.
And that's it. Now we are ready to write some code to use the Graph-API and retrieve SharePoint-Data from our Microsoft365 account.
2. Using TypeScript to query the API
2.1. Install the necessary libraries
$ npm install @azure/msal-node node-fetch
MSAL stands for Microsoft Authentication Library and
@azure/msal-node is the official library to authenticate with Azure AD from a Node.js Application. It kind of suffers from the same bloated and confusing documentation as the Graph-API, so finding the right classes and functions to use can be very cumbersome. However, we will see in the code below that we really only need a few lines to make it work.
We are also installing node-fetch as the Graph-API is an http-endpoint – but you can use any other http-library you fancy.
If you found the official client @microsoft/microsoft-graph-client on npm – do not use it. At least according to their documentation, it is not compatible with the simple credential-authentication that we are trying to use here.
2.2. Create a file queryGraphAPI.ts
Have a look at the full code-example first and explain the details after:
import * as msal from '@azure/msal-node';
import fetch from 'node-fetch';
// 1.
const tenantId = '<YOUR_TENANT_ID>';
const clientId = '<YOUR_APPLICATION_ID>';
// 2.
const clientSecret = '<YOUR_CLIENT_SECRET>';
// const clientSecret = process.env.CLIENT_SECRET
const clientConfig = {
auth: {
clientId,
clientSecret,
// 3.
authority: `https://login.microsoftonline.com/${tenantId}`
}
};
// 4.
const authClient = new msal.ConfidentialClientApplication(clientConfig);
const queryGraphApi = async (path) => {
// 5.
const tokens = await authClient.acquireTokenByClientCredential({
// 6.
scopes: ['https://graph.microsoft.com/.default']
});
const rawResult = await fetch(`https://graph.microsoft.com/v1.0${path}`, {
headers: {
// 7.
'Authorization': `Bearer ${tokens.accessToken}`
}
});
return await rawResult.json();
}
export {
queryGraphApi
};
How this code works
You will recognize the tenantId, clientId from step 1.1. – fill them in here directly.
The clientSecret from step 1.2. is sensitive information, so you should not be using it in your code and never commit it to your repo. For a quick test it's okay, later on you better provide this value through an environment variable.
The authority is the endpoint that the msal-library will authenticate with. Maybe now it is clear why you need the unique tenantId – it let's the generic login-endpoint of microsoft know for which account your are trying to authenticate.
We are using the
ConfidentialClientApplicaton
-Class ofmsal
. It is named confidential as our application is running completely in the backend where theclientSecret
is safe. There is a differentiation topublic
clients as themsal
library also offers authentication mechanisms for browser-based applications where using a general secret would not be secure (everyone could read and use it).As you might see, we do not query the Graph-API with our credentials directly, but we only use them to get an access-token. If you are familiar with the OAuth2 and OpenID stack, you might recognize this pattern. If not don't worry, I'll talk more about it in the second part.
With the scope, we tell the auth-endpoint that we want the token to be allowed to access the Graph-API with the
.default
-permissions – which are the ones we configured already in Step 1.2. For our use-case, this is the only possible option, but again there are other use-cases where setting other values here makes sense (which – you guessed it – will be covered in the second part).Finally, we query the Graph-API endpoint with the retrieved token. The
path
Parameter defines which data to query, and in 2.3. we will use it with/sites/root
which is the SharePoint Endpoint.
2.3. Use the API in an index.ts
import { queryGraphApi } from './queryGraphAPI.ts'
// In case you don't have top level await yet
async function start() {
const sharePointSite = await queryGraphApi('/sites/root');
console.log(sharePointSite);
}
start().then(() => console.log('Complete.'));
Now if you start the program you should see a result like this:
$ npx ts-node index.ts
{
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#sites/$entity',
createdDateTime: '2021-03-13T12:54:48.047Z',
description: '',
id: 'davelosert.sharepoint.com,0d19a070-63ba-40f8-872a-f83df5120b2a,770d1b68-3e43-44f2-aab4-ffc0bfe8c6a1',
lastModifiedDateTime: '2021-04-02T15:21:33Z',
name: '',
webUrl: 'https://davelosert.sharepoint.com',
displayName: 'Communication site',
root: {},
siteCollection: { hostname: 'davelosert.sharepoint.com' }
}
Complete.
Of course, this is just really simple code for a first working solution, but it's enough if you just need to query your data once in a while. If you plan to query the api more often, you might consider caching the token rather than requesting it on every query. The msal-library already supports caching
by providing a plugin and you can find an example on the Github Documentation – but covering this is out of scope of this post.
3. Using the Graph-Explorer to find the right query
Okay so now you should be able to query the SharePoint-Root-Site. However, I am pretty sure this is not the data you are after. So where to go from here?
One option is to start looking in the Graph-API reference documentation to get an overview of what is possible.
A more interactive and my recommended approach is using the official Graph-Explorer. This browser-based application lets you play around with and query the Graph-API and get immediate feedback about the responses. This makes it a great tool to find out both, the exact path and query as well as the permissions you need for your use-case.
The Graph-Explorer is mostly self-explanatory, so I won't get into too much detail here. But an approach to find the right query might be:
- On the left, select a Sample Query that comes closest to what you are after
- Run the query.
-
You might need to give consent in the tab Modify permissions so the Graph-Explorer is allowed to query the data in your name.
Do you recognize this pattern? This is bascially the same as the admin-consent we gave in 1.3. – but this time for the Graph-Explorer App and rather than in the name of an admin, you give it in the name of **your* account.*
-
The Modify permissions tab is also a great place to see which permissions are required to query the endpoint. However, it sometimes shows more permissions than you need.
For example for the sample query SharePoint Sites / my organization's default SharePoint Site (which is our query from Step 2), it shows both,
Sites.Read.All and Sites.ReadWrite.All. But as we saw, the former is enough to read and the latter is only required if you also plan to write to SharePoint.Once you know which permissions you need, you can add them in the App registrations page like we did in Step 1.3.
Refine the Query until the output matches what you are looking for.
One last thing to consider:
As described, the Graph-Explorer runs in the context of your logged-in user, not your application. This means even if you consent here, your App will not automatically get those permissions. As explained in 4., you still have to explicitly add them to your app.
Summary
As you might have seen, getting started with the Graph-API can be quite challenging. Especially the authentication and authorization part are a bit more complex to set up and not very intuitive at first.
But once you know what to do and where to look, it is only some clicks and a few lines of code to make it work. For the serverless-function described in the intro, it took me hours to make it all work. But registering the application and setting up the code examples for this post only took me around 15 minutes.
So hopefully I reached my goal to save you the hours of work and get started with the Graph-API more quickly.
As announced, I will soon publish a second blog-post where I'll go a bit deeper into the theory and concepts behind all of it.
However, if you have any feedback or open questions, feel free to comment below and I'll answer as soon as possible.
Top comments (0)