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.
Microsoft recently announced Teams management capabilities through the Microsoft Graph API. Graph is a RESTful API that can be called to manage many of Microsoft cloud services. For example, you can write an application or a scheduled PowerShell script that calls Graph to manage Azure AD, Office 365 and Intune, all through the same API. A long asked for API feature, the ability to pragmatically collect the PSTN call records and charges from Microsoft Teams, e.g. The phone bill.Up until now, you have only been able to get this by exporting it from the Teams Admin Center into Excel, but now, in beta, you can collect these records from Microsoft Graph.
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.
This article will explain the options for building tabs for Microsoft Teams which directly call the Microsoft Graph. The same methods apply to Task Modules (modal dialog boxes).
This article presents four options for building a (fully coded) tab or task module in Teams:
This approach, which became Generally Available at Build 2020, provides an extremely seamless user experience. It is documented here and the sample is here.
That is really easy! But unfortunately it’s really limited; the token you get is an ID token not an access token, so while it does identify the user it can’t be used to access the Microsoft Graph or other Azure AD secured resources. Even if you wanted to do the simplest Graph API call to read the user’s information (https://graph.microsoft.com/v1.0/me/), you need user.read permission, which is not included here. To do that, you need to implement a web service which uses the On Behalf Of flow to trade the ID token for an access token that includes the permissions you’re looking for. It’s not a ton of code, but you do need to stand up a web service to do it.
Then there’s the issue of consent. There are really two choices here: either get an administrator to consent on behalf of all users during setup, or support a pop-up that allows the user to consent at runtime. This pop-up uses the same mechanism as the older non-SSO approach (approach #3).
You can find a full sample with detailed instructions here.
NOTE: The On Behalf Of flow used in the web service requires use of a Client Secret to prove the application’s identity. Secure storage of the client secret is extremely important; this is easily handled in Azure by storing the secret in Azure Keyvault and using an app setting that contains a Keyvault Reference.
If you host your tab in SharePoint, you don’t have to worry about getting an access token; SharePoint handles this for you! However at the time of this writing, there is a caveat: by default, any permission you give your tab is also given to every 3rd party web part in your tenant’s SharePoint installation. SharePoint provides an isolated mode for web parts which addresses this, but it currently doesn’t work with Teams tabs.
Begin by creating a Teams tab with SharePoint Framework. Then specify the resource and permission you need in
config/package-solution.json. For example, to read the user’s email you would include:
When the web part is installed, an administrator will need to approve the permission in the API access screen in SharePoint online administration.
Calling the Microsoft Graph is really simple since SharePoint Framework takes care of it for you.
A full sample is available here.
Azure AD and many other authorization services don’t work in an IFrame, and Teams tabs are IFrames. To solve this, Teams provides a browser pop-up and the ability to send information between the pop-up and your tab.
One advantage of this is that it works with any browser-based authorization service. However in this case, we want to call the Microsoft Graph, so we’ll be calling Azure AD. From Azure AD’s perspective, the pop-up window is a fine place to log the user in, and the access token it provides can include any user delegated access without the need for an On Behalf Of token exchange. That means no server-side code is required, so your solution could be hosted in any static site, such as Azure Static Websites or Github Pages.
authenticate call in the
authentication namespace doesn’t do authentication! It only launches a pop-up; your app needs to handle the authentication and return the resulting access code and any other relevant information. In this sample, it returns the username and expiration time, so the app can request a new access token when needed.
The code inside the pop-up really consists of three parts:
With the older Implicit flow, Azure AD returns the access token on the URL. A safer approach, used in Auth Code PKCE and MSAL 2.0, returns an authorization code on the URL, which is then exchanged for the access token. Either way, your code needs to handle the redirect back from Azure AD to get this data.
The handleRedirectPromise() call checks the browser’s window object for a URL containing an auth code; if found, it handles the auth code and removes it from the URL; if not, it does nothing. When this is done, the MSAL 2.0 client object contains the information needed to obtain an access token.
The user is logged in using this call:
This is one of two options from MSAL; the other is
loginPopup(). In this case, Teams needs to handle the popup so the
loginRedirect() call is used; this redirects the whole web page (in the Teams popup) to Azure AD to log the user in. The response contains the auth code and is handled as shown above.
With any luck, the scopes required were specified in the login request, and the access token is already there inside the MSAL 2.0 client object; it can be retrieved using the
acquireTokenSilent() function. However, if additional permission is needed, the code needs to go back to Azure AD; this is handled by
acquireTokenPopup() for non-Teams applications).
Here is a snippet from the sample which tries to get the access token silently but, if it’s not there (either because additional permission was needed or the token has expired), it does another redirect back to Azure AD.
This is all well and good for the Teams desktop and web clients, but the mobile clients currently don’t support the Teams pop-up. In the mobile clients, the tab is just a regular web page; hence the MSAL calls can be done directly. The sample handles this by providing a second version of the tab that calls MSAL directly.
You can see how this works in the app.js component; it first checks for a redirect and then to see if it’s in an IFrame or not. If it is in an IFrame, it renders Tab.js, which uses the Teams popup; if not, it renders Web.js, which does not.
The Microsoft Graph Toolkit is an easy-to-use library of web components for calling the Microsoft Graph; it includes an auth component that includes Teams support! The result is an application that uses the pop-up mechanism with an older auth library, so it uses Implicit Flow. This may change in future versions of the Toolkit.
My colleague Ayça Baş has written a detailed article which walks you through creating this solution.
The next article in the series will consider calling Graph from Bots. If you’re building a messaging extension for Teams, see Markus Möller’s excellent blog series.
I was asked to deliver a daily extract of information about our organization’s Microsoft Teams adoption. It was to include the Team’s settings, its owners, its members (separated by internal members and guests), and when it was to expire. Ideally, I’d be able to schedule this to run each morning and drop the resulting file in a Team’s Channel document library.
My first approach to the problem was to just see what I could do without thinking too hard. So, with the
MicrosoftTeams module, I knew I could retrieve info about all the teams. The downside is that I would have to be elevated to something like “Team Services Administrator” in Azure AD.
$Teams = Get-Team
Getting the team members would be a little more difficult because the commands that get that info are in the Exchange Remote Admin module.
The last requirement was to provide a list of the Teams currently in the recycle bin along with when they’d been deleted. That took the AzureADPreview module.
There are a few problems with these parts to a solution:
Could I use an App Registration in Azure AD to connect to Teams and AzureAD? Yes! But… Neither the classic Exchange Admin module nor the new ExchangeOnline module support App Registration authentication. What do all these modules really have in common? The Microsoft Graph API.
So, I started with the great guest post by Alex Asplund on the Adam The Automator blog. His post walks through creating the App Registration. I hadn’t created an App Registration before, but I had the rights to elevate to Global Admin to create one and assign the right application permissions for Graph API:
I chose to use the certificate method of authenticating for the OAuth token and thus created a self-signed Certificate, then exported and uploaded it to the App Registration.Again, I used the code snippets in the blog to create an OAuth token to use with Graph API requests.
One of the requirements was to provide the expiration date of each group. Researching the outputs of various teams- and groups-related graph endpoints, the creation date and the last-renewed date are returned. We need to be able to calculate the expiration date, and for that we need to know what the lifecycle policy is for the groups.
The request returns you a collection (of one, in my case) of Group Lifecycle Policies with all of the policy’s properties including the
groupLifetimeInDays value that we’re after.
Now we want to get the groups. We want to end up with the Teams, and Teams are “Unified” groups in Azure AD. The following will get the Unified groups in your organization:
Note: When using the OData queries via PowerShell, you need to be careful to include the backtick that escapes the dollar sign, otherwise PowerShell is going to try and evaluate
Another thing to be aware of is the Graph endpoints generally return just 100 items per page, but that response includes a link to get the next page. So, to deal with that and get all of the groups, use a
That gives us all of the Unified groups, but not all Unified groups are Teams!
We know that going and getting the Team object, the members, and the owners is going to involve a lot of waiting for
Inovke-RestMethod responses, so let’s start by leveraging some parallelism with:
Another point where we can achieve some efficiency is with making batch requests to the Graph API using the
'https://graph.microsoft.com/v1.0/$batch' endpoint. Again, be mindful of your single vs. double quotes here. By using single quotes in this case, we avoid the dollar sign issue. Using the batch endpoint is well documented at https://docs.microsoft.com/en-us/graph/json-batching?view=graph-rest-1.0 . Create a hashtable key-value pair that is a collection of hashtables, one for each of the requests you want to submit in the batch. Convert that to JSON, and submit it as the body of the request.
You can see above that we’re getting the Team, the members, and the owners, all keyed by the
ID property. Submit that block of JSON to the
$batch endpoint and parse the results:
The advantage here is that we only make one request instead of three transactions. For the members, we’ll deal with pagination in the same way as with the Teams themselves.
Distinguishing the “guests” from the internal “members” is just a matter of examining the User Principal Name (UPN); AzureAD guests will have “EXT” in middle of the UPN.
We’ve collected all the information we need for the team, we only need to calculate that expiration date before we generate some output:
It’s a matter of outputting a
[pscustomobject] with all of the team properties.
The last requirement I was presented with was to provide a listing of all of the Groups that were in the recycle bin. That’s found with the Graph endpoint
$DeletedGroupsURI = 'https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.group' As with the Groups themselves and the members, the results are paginated and are handled the same ways as before. My organization wanted an Excel spreadsheet so I used Doug Finke’s ImportExcel module to export the collections.
Working with the Graph API can allow you to customize your approach to AzureAD and Teams management. Using the modern App Registration authentication acts not unlike JEA, in that an unprivileged account can be enabled to perform specific limited tasks. In this case we were able to efficiently gather all of the information we needed about the state of our Teams environment, while managing and maintaining control over access. With no elevated access required, “who” the script runs as becomes less important, as long as that entity has access to the file location.
The full code for this solution will be available as TeamsReportByGraph.ps1
Edited by AJ Lewis