
22 November 2023 (8 minute read)
Silent user authentication in a Microsoft Teams application
Intro
At SimplyDo, we are constantly looking for ways to extend the accessibility of our end-user facing platform. We expect that our end-users (the idea creators) will access SimplyDo in a wide variety of hardware and platforms. One of the biggest hurdles we face is supporting this diversity in addition to expanding the usefulness of our product.
One of our biggest challenges is, consistently, the initial step of getting users into the platform for the first time. Providing the motivation and simplicity to entice unique users is a compelling challenge, and a problem shared by practically any software platform. One of our potential solutions to this was to create a version of our platform that operates solely as a Microsoft Teams app; something that can be pre-installed by organisation admins and doesn’t require users to familiarise them with something new. We hoped that emulating existing patterns and visuals from Microsoft’s provided UI framework, Northstar, would ease users into using SimplyDo, reducing the complexity of learning an entirely new platform.
There are a number of details and intricacies to this development process I won’t go into here. This article focuses on what we perceive to be the biggest advantage of this entire undertaking – removing the hurdle of user sign-up/login by utilising silent authentication. That is, leveraging the fact the current user is already logged into Microsoft Teams, then going through a token exchange process with our servers to provision a SimplyDo user account.
Unfortunately for us, this was a relatively novel process within Microsoft Teams, and documentation for this process was disparate. Microsoft have some surface level blog posts, but following our own trial-and-error, we want to share an overview of the process for anyone else who wants to include a similar procedure in their Microsoft Teams application stack.
The following information assumes usage of a React/JavaScript frontend application, a Python and/or JavaScript backend, the JavaScript package @microsoft/teams-js, and the JavaScript or Python Azure package msal. Some basic knowledge of Microsoft architecture is also assumed; for example, the idea of a tenant ID (unique to the “organisation” using your application) and a client ID (unique to your application).
Step 0: Providing Admin Consent
We hit this wall after developing our initial silent authentication process, but in retrospect, it makes total sense. Microsoft do not want any random service to access a user’s Microsoft account login tokens, for obvious security reasons. Therefore, before any silent authentication can take place, an organisation admin must provide permission for your platform to access this.
Fortunately, due to the aforementioned @microsoft/teams-js
library, this is straightforward, if a little cumbersome.
Assuming your platform requires a user to login, the main restriction with this is that your application is unusable from an initial installation until an organisation admin provides consent for users to your platform to authenticate with Microsoft. Additionally, Microsoft does not provide a way for a frontend application to check whether admin consent has been granted until any given users authentication fails, neither can we identify whether the current user is an admin or not. Therefore we need to keep track ourselves (on the SimplyDo end) when admin consent has been granted. Hopefully in the future Microsoft will provide a way to retrieve this information from them, as currently if admin consent is revoked for whatever reason, you will have a data mismatch; your application will assume admin consent has still been granted.
Firstly, and this applies to any instance where you will be using the Microsoft authentication libraries, we must initiate the authentication.
await microsoftTeams.authentication.initialize();
Next, we initiate an authentication flow, which is again a pattern you will see throughout this process. The authentication flow opens a new secure window to access Microsoft’s identity provider; a new window is required because this content is blocked from appearing in an iframe
. At this stage, your application is required to be aware of the current organisation’s Microsoft Tenant ID. There are a number of ways of doing this, I won’t go into them here.
// Open the authentication flow window
microsoftTeams.authentication.authenticate({
url: window.location.origin + "/yourapp/adminConsent?tid=" + organisationTenantId,
})
.then((result) => {
// On successful admin consent granting, store this somewhere
})
.catch((error) => {
// Display an error message
})
The previous snippet opens an authentication window to a page in our application. Following from that, we will redirect the user to Microsoft’s identity provider.
const provideAdminConsent = useCallback(async () => {
if (tenantId) {
const queryParams = {
client_id: "Your app's client ID",
redirect_uri: window.location.origin + "/yourapp/adminConsentEnd",
scope: ".default"
};
const consentEndpoint = "https://login.microsoftonline.com/" + tenantId + "/v2.0/adminconsent?" + util.toQueryString(queryParams);
window.location.assign(consentEndpoint);
}
}, [tenantId]);
useEffect(() => {
provideAdminConsent();
}, [provideAdminConsent]);
This will bring the current user through the traditional Microsoft authentication flow, after which they will be asked to provide admin consent for users to authenticate with your application. Agreeing to provide admin consent will trigger a success response; this automatically closes the popup window and runs the .then(() => {})
from the original microsoftTeams.authentication.authenticate()
call. This is where your application should record that admin consent has been granted, so the admin consent option isn’t shown to users going forward. From Microsoft’s perspective, your application now has permission to access their identity service, which is required for the rest of the silent authentication process.
Step 1: Acquiring the auth token
Now that we have permission to access users’ authentication tokens in our application, the step of acquiring the token is luckily extremely simple. The key to this is the silent part of the silent authentication process; the authentication occurs without the users’ knowledge, requiring no input from them.
microsoftTeams.authentication.getAuthToken()
.then((authToken) => {
// Handle success
})
.catch(() => {
// Handle failure
});
This snippet is all that is required to access the users authentication token. Nonetheless, Microsoft recommend that your application provides a manual option (e.g. username/password) in case of failure when getting the token.
Step 2: Using the auth token
This step will demonstrate how we use the token we acquired from the user in Microsoft Teams. From here, we move from our React Teams application to our backend. In our case, our authentication service is using Node
with the @azure/msal-node library - We use this to get information about the user from Azure Active Directory, which in turn we will use to provision a user in our application.
const msalClient = new msal.ConfidentialClientApplication({
auth: {
clientId: your_microsoft_client_id,
clientSecret: your_microsoft_client_secret
}
});
const result = await msalClient.acquireTokenOnBehalfOf({
oboAssertion: your_user_teams_token_here,
skipCache: true,
authority: `https://login.microsoftonline.com/${your tenant id}`,
});
In the first part of the code snippet, we construct an msal
client to allow our authentication service to communicate with Azure Active Directory. Your clientId
and clientSecret
are found in your Microsoft Application Portal, and are unique to your application.
In the second part of the code snippet, we exchange the Teams authentication token for a users Azure token - allowing us to access the Azure API on behalf of the user that provided the token. We will use the Azure API to get information about the user.
The following snippet is highly generalised and will depend massively on the specifics of your organisation. The result.access_token
acquired from the acquireTokenOnBehalfOf
allows you to access user profile data from Azure Active Directory, depending on your app client’s scope. We use some of this information to construct a SimplyDo user account on behalf of this user; we will demonstrate this in the following code snippet, but there are a number of potential options from this point and I recommend consulting the @azure/msal-node
documentation.
request.get(`https://graph.microsoft.com/v1.0/me?$select=${fieldsToFetch.join(',')}`, { auth: { bearer: result.access_token } }
We use the /me
endpoint on behalf of the user to request their user data – we then construct a SimplyDo user using their name, email address, job title and job department, where applicable. The user data we requested is dependent on the scope requested of that given user, e.g. User.Read.
Step 3: Finishing up
We have finished the Microsoft specific parts of the authentication process; we have used the user’s Microsoft token to create a SimplyDo (or your application) user on their behalf. At least until we need to reauthenticate with Microsoft at some point, we have completed the silent authentication process. From here your next step is very likely to produce an API token, which can then be used by the Teams application to interact with the backend henceforth.
Outro
Our hope is that this article distils the most important steps of Teams silent authentication. We collectively spent a fair amount of time digging through documentation for what ended up as quite a small amount of code to achieve the main objectives of silent authentication. I recommend reading up on what each function does to understand the implications of what each does, ensuring you don’t misuse your users’ data in any way.
I have personally been motivated for some time to “give back” a tutorial having utilised so many Medium articles in my process to becoming a software developer – this process finally gave me the opportunity to provide some unique insight that I personally would’ve found useful when I was developing it. If this helps you or you have any questions, please feel free to contact me at niall@simplydo.co.uk.