Securely connect to Microsoft Graph from Teams tabs using SSO
Say you’re building a Microsoft Teams tab with Single Sign-On. In your tab, you want to connect to Microsoft Graph to show some data from Microsoft 365. Here is how to do it securely.
Do you use Single Sign-On in your Teams tabs?
When you’re building Teams tabs, Single Sign-On (SSO) is a must. It significantly improves the user experience. After all, if a person is signed in to Teams, why should they have to sign in to your app again? Teams tabs SSO allows you to automatically sign users to your app with the same account that they use in Teams. But SSO comes with some considerations.
Enable SSO in your Teams tab
Enabling SSO in your Teams tab comes down to a few configuration settings. In your Azure AD app registration, you need to configure the API URI, define an API scope, and authorize Teams client applications to access your app.
Next, in your Teams app’s manifest, you’d specify the domain where your app is hosted, the ID of your Azure AD app, and the API URI you defined previously.
With this setup in place, when users open your Teams tab, you will be able to sign them in automatically and get an access token from the Teams SDK.
microsoftTeams.authentication.getAuthToken({
successCallback: (token: string) => {
const decoded: { [key: string]: any; } = jwt_decode(token) as { [key: string]: any; };
setName(decoded!.name);
microsoftTeams.appInitialization.notifySuccess();
},
failureCallback: (message: string) => {
microsoftTeams.appInitialization.notifyFailure({
reason: microsoftTeams.appInitialization.FailedReason.AuthFailed,
message
});
},
resources: [process.env.TEAMSSSOTAB_APP_URI as string]
});
You can use this token to get some basic information about the user, like their name or email.
If you want to call Microsoft Graph in your Teams tab, you will need to obtain a new token for it.
Exchange the Teams SSO access token for an API token using the on-behalf-of flow
For simplicity, we’ll assume in this article that you use the same Azure AD app for Teams SSO and to secure your API.
To exchange your Teams SSO token for a token to call Microsoft Graph, you will need to extend your Azure AD app registration with a secret.
Next, in your code, you will need to use the on-behalf-of OAuth flow. It’s a flow that requires using your app’s secret, and which you shouldn’t do from the client. Instead, you would do it from code running on the server where you can hide the secret from users.
If you’re not using an SDK, like MSAL, you would execute the following web request:
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
content-type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&client_id=ea93a2b7-0bfd-4238-b551-b125d14762b4&client_secret=abcdef12-C4~1HJKuWyJHZ~GHIJKL12.&assertion=eyJ0eXAiOiJKV...&scope=https://graph.microsoft.com/.default&requested_token_use=on_behalf_of
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
and requested_token_use=on_behalf_of
denote that you want to use the on-behalf-of flow. client_id
and client_secret
are respectively the ID and secret of your Azure AD app. assertion
is the access token you received from Teams SSO. Finally, scope=https://graph.microsoft.com/.default
specifies that you’d want to request a token with all Microsoft Graph scopes listed in your Azure AD app registration. You can also choose to use dynamic consent instead, and list here only the scopes you need at this moment.
Getting token for Microsoft Graph in a Teams tab with SSO
To exchange the access token you got from the Teams SDK as a part of Single Sign-On, you’d use the on-behalf-of OAuth flow. The Teams SDK is client-side. But the on-behalf-of flow needs to be executed server-side because of the Azure AD app secret that you shouldn’t expose to users. There are two ways to exchange the Teams SSO token for one to call Graph.
Exchange the token on the server and return the Graph token to the client
The first approach is to build an API that exchanges the Teams SSO token for a Graph token and returns it to the client. The API itself is anonymous and accepts the Teams SSO token either via the body in a POST request or the query string. You see this approach being mentioned a lot in the context of Teams SSO and Graph because it’s relatively simple to implement.
After completing SSO, the Teams tab calls the API to exchange its Teams SSO token for one for Graph (1). The API calls Azure AD and exchanges the token using the on-behalf-of flow (2). It then returns the token to the client (3) which uses it to call Microsoft Graph and retrieve data from Microsoft 365 (4).
On one hand, this setup is convenient. Since the API is anonymous, it’s easy to implement. The amount of server-side code is limited to the on-behalf-of flow, and calling Graph is done directly from the client.
But on the other hand, it does seem odd. Wouldn’t hosting an anonymous API that exposes a token obtained on the server to the client be considered a security risk?
Typically, you wouldn’t host anonymous APIs unless they were meant for public consumption. And even then, you would want to implement some kind of authentication to control their usage. Also, in the identity world, sending access tokens from the server to the client is something you would definitely not do.
Yes, anyone can call your API because it’s anonymous, and yes, you’re exposing a token you obtained on the server to the client. But you can only get that token if you send a valid SSO token to your API. If you send an invalid token, the on-behalf-of flow will fail, and you won’t get a token for Graph.
Still, while the token you send to the API will be validated during the on-behalf-of flow and there is little harm exchanging the token can do by itself, it’s recommended that you secure your APIs and validate any input that your API receives. Here is an alternative approach that you should consider instead.
Call Graph server-side and return only the data
Another way to call Microsoft Graph in a Teams tab using SSO is by building a secure API that calls the Graph and returns to the client just the data it needs.
The basics of the setup are still the same. Your app calls the API with the Teams SSO token (1). This time time, because the API is secured with Azure AD, before the code executes, the access token sent in the request will be validated by Azure AD. Next, the API calls Azure AD using the on-behalf-of flow to exchange this token for one for Graph (2). Rather than returning the token to the client, the API calls Graph (3) and returns to the client the data it received from Graph (4).
The nice thing about this setup is that the API itself is secured with Azure AD and calls to Graph are implemented on the server. The access token for Graph, obtained using the on-behalf-of flow, never leaves your API and your client only gets the data it needs.
If you built your API on Azure Functions, you could easily secure it using EasyAuth. To have the API accept the Teams tab SSO token, you’d update the EasyAuth configuration by adding your AAD API URI to the list of Allowed Token Audiences.
The downside is that all calls to Microsoft Graph need to be implemented in your API which becomes a proxy between your app and Graph. What you gain though is an additional layer of security, which for some organizations, is an important factor for choosing the architecture in their apps.
Summary
Implementing Single Sign-On in your Teams tabs improves the user experience. Because people are already signed in Teams, they don’t need to separately sign in again to your app.
If you need to call Microsoft Graph in your Teams tab, you will need to exchange the SSO token for one to call Graph. When implementing the API to exchange the token, you should consider security and validate any input it receives before proceeding.
Thanks to Bob German, John Patrick Dandison and Wictor Wilén for their input and feedback.