Microsoft Teams applications almost always need to call the Graph API, yet it’s not as easy as just calling a REST service. Most of the complexity has to do with getting an Azure AD access token, which is required on every Graph call to establish what, if anything, the caller is authorized to do.
Getting the access token requires an understanding of Teams, Azure AD, Graph, and sometimes other components like the SharePoint Framework or Bot Framework, yet each of these is documented separately and each one assumes the reader knows all the others. I literally get questions every day from frustrated developers trying to figure this out! (Yesterday I got 3!) In 2 1/2 years of Teams app development, this by far the most common source of difficulties.
I wrote these articles hoping they’ll assist developers in calling the Graph from Microsoft Teams. They’re also companions for my talk, “Calling Microsoft Graph from your Teams Application”, at the PnP Virtual Conference 2020.
- Introduction (this article)
- Deep dive concepts (optional)
- Calling Graph from a Teams tab
- Calling Graph from a Teams bot
This article will explain the basics. If all goes well, you can follow the step-by-step instructions in one of the sample apps and be done with it! Some day, the tooling may be improved to automate some of the steps.
The “deep dive” article is for those who are curious or who need to understand more detail to handle a specific situation or to aid in troubleshooting.
The articles which follow target specific scenarios for calling the Graph from a tab or bot in Teams. Task modules work the same as tabs, so refer to that article. For messaging extensions, please see Markus Möller’s excellent blog series.
Here’s the official Authentication page in the Teams developer docs and here’s the one for Authentication from a Tab. If these meet your needs please skip all this!
While these articles are intended to stand on their own, they’re a companion for my talk at the PnP Virtual Conference on September 1, 2020.
Why the Graph API?
Just as Microsoft Teams is a unified user interface for many services all across Microsoft 365, the Microsoft Graph is a unified API across these services. It provides a single REST endpoint, https://graph.microsoft.com, that can access data and insights across Microsoft 365 services. (Some government and national clouds use different URLs.)
There are a number of advantages to going through the Graph as opposed to calling Microsoft 365 services directly:
- Applications only need to deal with a single service endpoint for many services, allowing for improved scalability of both clients and services. Thus, overhead such as DNS lookups and authorization flows are shared across many services, allowing apps to perform better.
- Graph can cache and relate information between services. For example it provides a “me” keyword on many services, for which the application might otherwise need to look up information about the user before making a call to, say, the user’s list of contacts.
- Graph is a lot more consistent than the individual service APIs, making it easier for developers to learn and reuse code. For example, sorting a list of email and a list of files both use the same OData syntax; this was not true of the native Exchange and SharePoint APIs.
- The overall system of clients and services scales better. Given c clients and s services, going through an aggregator like Graph means you have only c+s connections in the system vs. c*s if each client called each service directly. This topology also provides a central place to log and trace requests across multiple services, making it easier for Microsoft to support the environment.
Newer services, such as Microsoft Teams, don’t provide native API’s, they only use the Graph. So, for example, if your app needs details about the Team it’s running in, it needs to call the Graph. Older services like Exchange and SharePoint still have native API’s; you might need them if the feature you need isn’t in the Graph (yet). But long term, Microsoft is moving everything to the Graph!
If you’re new to the Microsoft Graph, a great place to start is the Graph Explorer. It allows you to play with queries across all the services, and has many samples to get you started. If you log into the Graph you can see your own content; if not, you’ll see sample content.
The user is already logged into Teams, why can’t my app just call the Graph?
I get this question a lot. The concise answer is, “because your app is not Teams.”
To explain this further, consider the way traditional, old-school operating systems work.
- An application is just a file in the file system; users can run any application they want.
- An application has the same permission as the user who is running it. For example, if I can read my contacts list, and I run an application, the application can read my contacts list.
This approach is still widespread in operating systems that have been around for a while (like Windows, MacOS, and Linux). As a result, many people assume that’s how the world works.
The problem is that users have no way of knowing what an app might do when they run it. For example, a user might play a game, and the game has a hidden “feature” of reading the user’s contact list and sending them all a bunch of unsolicited email. Of course the user has permission to access her contacts, but the game probably should not!
To prevent this, modern operating systems apply permissions to applications as well as people. This is true of most mobile operating systems like iOS and Android, and it’s also the case for cloud-based applications secured by Azure AD. Here are the new rules:
- Apps need to be registered (just like users)
- Apps need permission to access resources (just like users)
- A user or administrator must consent to these permissions on an app by app basis
For example, here’s a game running on an Android tablet. It wants to read my contacts! I wonder why?
Now consider the architecture of a Teams application.
The application appears in the Microsoft Teams UI but it’s still a separate application which runs outside of Teams. So this is why, in order to read your contacts (or access any of the many services available in the Graph API), you need to:
- Register your application with Azure AD (which the Graph and all its underlying services use for authorization)
- Specify the permissions (scopes) your app needs for specifc resources (such as Contacts.Read)
- Request an access token from Azure AD and include it in the HTTP header when calling the Graph API. This lets the Graph know what you’re allowed to do.
Azure AD will authenticate the user and, the first time your app asks for a permission, it will ask the user to consent. If it’s a really big permission, such as Group.ReadWrite.All then an administrator needs to consent as well.
The Graph Explorer is an app from Microsoft that’s registered in Azure AD. If you log in (to see your own data and not the sample), you can click the “modify permissions” button to select the permissions it will get. Graph Explorer will request an access token with those permissions, just like any app, and you may be prompted to log in or consent. Some permissions require an administrator to consent; if you’re not an administrator in the tenant, you won’t be able to grant those permissions although a tenant administrator could grant them.
How do user and app permissions work together?
When your application wants to access resources owned by another application (such as Microsoft Graph), there are two kinds of permissions available:
- User delegated permissions allow an application to act on behalf of a user. Therefore, the effective permission is the intersection of the user’s permission and the application’s permission. For example, suppose Alice has access to files A, B, and C, and the application has Files.Read permission. When Alice runs the application, it can read files A, B, and C.
- Application permissions allow an application to act on its own behalf. Therefore the effective permission is generally the application’s permission across the whole tenant. This is often used for daemon services or to elevate permission beyond what the user can do. For example, if an application has Files.Read permission, it can read every file in the entire Microsoft 365 tenant.
Normally, your application will use Delegated permissions. (App permissions always require administrator consent and admins rightly think twice before approving such broad permissions. In general, they should strongly trust the person or company who provided it or review the application code.) App permissions are to be used sparingly, only when a user is not present or if the app needs to do something the user normally isn’t permitted to do.
A third option is emerging, which is called Resource-specific consent (RSC). This will allow a Team owner to grant app permission to a single Microsoft Team instead of the whole tenant. It’s still in Beta, however, and it only applies to Microsoft Teams itself and none of the other services that appear in Teams such as files (SharePoint and OneDrive) or calendars (Exchange). Due to those limitations, and the fact that this is already a pretty complex topic, RSC will not be considered in this article series.
Graph Explorer runs in a web browser which is a so-called public client because you can’t safely store secrets in a browser. In order to get Application Permissions, an app needs to prove its identity using an app secret or certificate; this just can’t be secured in a web browser. For that reason, Graph Explorer and all browser based applications can only have User Delegated permissions. The same is true for mobile apps. If you want app permission – to access a file the user doesn’t have permission to, for example – you’d have to write a web service and call that from the browser or mobile device. The web service is presumably on a secure server and thus is allowed to handle the app secret.
What Graph permission do I need?
The Graph team has done a great job of documenting this. At the top of each article in the Graph reference you will see a summary of the permissions needed for each call. Notice that there are three kinds: Delegated (work or school accounts), Delegated (personal Microsoft accounts), and Application. This article doesn’t deal with the personal Microsoft accounts; you’ll normally be looking at Delegated (work or school accounts).
It’s a best practice to select the least privileged permission you need. For example, here’s the call to get information about a user;
User.Read is the least privileged choice. However if you also needed, say,
User.ReadWrite.All for something else in your application, that will allow this call as well.
So why an article on Teams apps, isn’t this the same for any application?
To get an access token requires knowing for sure who the user is – thus, the user has to prove their identity (log in). In Azure AD this is usually done via some kind of web browser – either a pop-up or hosted browser. This prevents applications from ever touching the user’s password or other secrets, and allows Azure AD to enforce policies like multi-factor authentication and conditional access. It also allows Azure AD to prompt the user for any missing consents.
The challenge here is in providing Azure AD a safe browser in which to prompt the user.
- Teams tabs and task modules are IFrames, and IFrames are not considered safe by Azure AD, so you can’t get an access token from an IFrame without some assistance
- Teams bots and messaging extensions don’t provide a browser UI at all
For that reason, Teams provides a number of different options to let Azure AD interact with the user when required in the authorization process.
All this means that it’s not enough for a Teams developer to understand the Graph and Azure AD; he or she also needs to understand the ways that Teams manages the situation so Azure AD has a browser window from which to prompt the user.
That’s the reason for this blog series!
German, B. (2020). Calling Microsoft Graph from your Teams application – Part 1: Introduction. Available at: https://bob1german.com/2020/08/31/calling-microsoft-graph-from-your-teams-application-part1/ [Accessed: 20th November 2020].