Seamless SSO login for Microsoft Teams Tabs
Tags: microsoftteams, security, webdev, azureactivedirectory
Published: 2019-11-17 13:17:44

# Maximize security AND user experience
Until now, it was only possible to build authentication in Microsoft Teams tabs by this flow: You spawn a popup, the user manually authenticates, and then (best case) you stored some sort of session token in the users locale storage so that you wouldn't have to re-authenticate the user everytime they want to use your tab. In the end, you had to take care of building the popup flow, managing sessions for the users and deciding on in which frequency you want to re-authenticate the user to make sure they are still in a safe context.

## How to build a secure and seamless experience
In a nutshell, you only need three things to implement a simple Single Sign On mechanism in Teams: An Azure Active Directory App registration, a Teams manifest and a HTML page that hosts less than 10 lines of JavaScript. You can find a complete implementation of this in [this GitHub repository](https://github.com/Urmade/TeamsTabSSO). Let's start with Azure Active Directory.

### Configuring an App registration
When acquiring a token for your user, Teams uses the so-called OAuth2 on-behalf flow. This means: Users use your app and give your app the consent that it can access their data. Your app then opens to a second app (that is managed by Microsoft, for simplicity it is referenced as *Microsoft App* throughout this tutorial) and allows that app to access everything that your app can access. The Microsoft app then acquires a new token (in your name), and returns you only an id_token that is then exposed to the Teams client. 

What are reasons for this way of doing it? For one, you don't have to care about implementing this whole flow yourself. The Microsoft App runs on a server that handles token acquisition, whilst you can sit back and wait for a token to return. Another nice side effect of this is that Microsoft automatically filters the answer it gets back from Azure Active Directory when it acquires the user token, and only gives you an id_token. This token already contains information about the user, but it cannot be used to make requests against any other service. Therefore the token is worthless upon the information it contains itself, minimizing the attack surface on the users client.
Secondly it is a more secure flow whilst enabling great flexibility. Usually when you want to receive any user tokens directly in the browser, you have to provide a client_id and a client_secret to the client. With these two values, basically everyone else could acquire tokens in your name as well. With the on-behalf flow, Teams only knows the client_id (and even that only through the manifest), and the Microsoft App can identify the id and acquire a token with its own, hidden id and secret.

But how do we actually register an App to work with Teams Single Sign On? 
> The process consists of basically three steps: Telling the Azure Active Directory that we want to have an application, giving it the permissions to read the users profile, and opening this application to the *Microsoft App* so that this app can access the users profile on our behalf. 

The process is well-documented in [this article](https://docs.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/auth-aad-sso#1-create-your-aad-application-in-azure), and you can find a step-by-step tutorial right here: 
1. Head to https://portal.azure.com and log in with your credentials.
2. Search for **Azure Active Directory** and select it.
3. On the AAD Dashboard, click on **App registrations** in the left-hand navigation.
4. Click on **New registration** in the top navigation bar.
    1. Give it a name that people understand. It will be eventually shown to them whilst using your app.
    2. In **Supported Account types** go for **Accounts in any organizational directory**.
    3. Leave the **Redirect URI** blank.
    4. Click on **Register**.
5. In your newly created app, click on **Expose an API**.
6. On **Application ID URI**, click on **Set**. The API must have the following structure: api://*yourendpoint*/*client_id*. For example: api://mydomain.com/00000000-0000-0000-0000-00000000000000.
7. Next, click on **Add a scope**. Give it the scope name **access_as_user**, make it consentable by users and admins and give it a display name and description that is understandable for your users. Make sure its State is **enabled**.
8. Click on **Add a client application**. 
9. Give the following client_ids access to your API: **5e3ce6c0-2b1f-4285-8d4b-75ee78787346** and **1fec8e78-bce4-4aaf-ab1b-5451cc387264**.
10. On the left-hand navigation, click on **API permissions**.
11. Click on **Add a permission**, then **Microsoft Graph**, **Delegated permissions** and select *email*,*offline_access*,*openid* and *profile*. Click on **Add permissions**.

Here you go, you have everything in place with Azure Active Directory to receive Single-Sign-On tokens!

### Creating a Teams app manifest
Basically all you have to do to enable SSO in your Teams tabs is to specify your API endpoint in the Teams manifest. 

To register a Teams application with a basic tab, you can follow [this article](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/app-studio-overview) as a starting point. There is only one additional step to configure Single Sign On: In App Studio, go to **Domains and Permissions** and click on **Set up** under **Web App single sign-on**. Here you have to provide the client ID that you got from the AAD app registration as well as the resource URL you specified (in AAD, it was called *Application ID URI*, e.g. api://mydomain.com/00000000-0000-0000-0000-00000000000000). That is all you have to do! If you want to configure these properties right in the manifest.json, you can follow [this documentation](https://docs.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/auth-aad-sso#2-update-your-microsoft-teams-application-manifest).

### Getting your single sign-on token
Now that all setup steps are done, you can implement the actual single sign-on in the tab. This can be done in only 5 lines of code:


    microsoftTeams.initialize();
    var authTokenRequest = {
        successCallback: function (result) {console.log(result)},
        failureCallback: function (error) {console.log(error)}
    };
    microsoftTeams.authentication.getAuthToken(authTokenRequest);


`result` will contain the id_token with the users information. This is a JSON Web Token and can be decrypted using various JWT libraries. This token contains the name, mail-adress and AAD ID of the user. If you only need this information, you are now officially done! 

## Limitations and workarounds
This new mechanism is optimized around giving you a quick, seamless and verified information around who is using your application. In OAuth2 terms, it *authenticates* your user (if your app uses Azure Active Directory as the identity control plane). This leaves some use cases open where you still have to implement some workarounds.

### Your app must use Azure Active Directory as its Identity provider
This flow only works with returing you the Azure Active Directory information about an user. If your app has its own Identity provider, you can't use this Single Sign On flow to authenticate your users. There of course is a way to still authenticate your users, but you are responsible for doing this and it usually involves a popup asking your users to log in manually. You can check out [this article](https://dev.to/urmade/building-a-microsoft-teams-connector-2bhp#securing-the-connector-adding-authentication) if you want to learn more about how to build such a mechanism.

### You cannot make any further requests with the token
In the process, you get an id_token returned. This token is only used to provide information about the user and cannot the used for *authorization*, meaning giving you access to any other confidential data that you could ask for from Azure Active Directory. In fact, the SSO mechanism only implements half of the On-behalf-of OAuth2 flow. The Microsoft App gives you a token that contains data that was accessed in the name of your app, but you still have to convert it into a token that can make additional requests, as only then it is truly *your* app that is accessing that data.

Converting a token that another app has scheduled for you into a token that you got yourself is well documented in [this article](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#service-to-service-access-token-request). A very simple implementation could look like this: When you get the token from the client, send it to your tab backend.

`fetch("/storeToken?token=" + result)`

In your backend (we're using TypeScript in this example) you can then trade the id_token for an access_token by following the OAuth2 specification. The most important value is the client_secret that verifies that you are the actual owner of this app who is allowed to make requests against Azure Active Directory / Microsoft Graph.

    app.get("/storeToken", (req,res) => {
    const idToken = req.query.token;
    request("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
        "method": "POST",
        "headers": {
            "Content-Type": "application/x-www-form-urlencoded"
        },
        "form": {
            "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
            "client_id": process.env.CLIENTID,
            "client_secret": process.env.CLIENTSECRET,
            "scope": "user.read",
            "requested_token_use": "on_behalf_of",
            "assertion": idToken
        }
    }, (error,response,body) => {
        const access_token = JSON.parse(body)["access_token"];
        })
    })

### You only get a certain set of scopes
When using SSO with Teams tabs, the token issued will only contain five scopes: `user.read`, `email`, `profile`, `openid` and `offline_access`. Oftentimes, this is not enough when your tab wants to learn more about the context of the user or enables them to work with their Graph data. Before you can exchange this token for an access_token with further scopes in your backend, the user has to initially consent that you are allowed to use this data. For implementing this you can refer to [this documentation](https://docs.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/auth-aad-sso#asking-for-additional-consent-using-the-auth-api).