# User Authentication in Conversational Bots

In this final part of the **ODC with Bots for Teams** series, we explore a way to authenticate a user within a Microsoft Teams conversation. Specifically, we examine how to quietly exchange a user's access token, issued when logging into Microsoft Teams, for an access token of our bot application. This token will have specific Microsoft Graph API permissions that we will use to send an email to the user.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Please note that this token exchange <strong>only works in the Microsoft Teams channel</strong> and other Microsoft Office applications, but not with channels like Slack.</div>
</div>

Authenticating a user within a Teams conversation is a significant topic. Besides the token exchange using Microsoft Entra, which is covered in this article, you can also authenticate a user with any OpenID Connect-compatible Identity Provider.

But when should you authenticate a user from within a conversation?

Explicitly asking a user to authenticate is not always necessary. You only need to require authentication when you need an access token to perform actions on behalf of the user (meaning with permissions associated with the user) on resources. Examples include:

* Interacting with the Microsoft Graph API, such as sending an email on behalf of a user. In this case, you need an access token issued by Microsoft Entra and permissions to send emails.
    
* Interacting with AWS Simple Storage Service. Here, you would authenticate a user with AWS Cognito to get an access token that you can exchange for AWS credentials via AWS STS.
    

You don't need to require user authentication just to know who the user is. The information sent as part of an **Activity** in the **From** and **Recipient** properties can be sufficient.

For more information on Bot Authentication, visit [User authentication in the Azure AI Bot Service - Bot Service | Microsoft Learn](https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-concept-authentication?view=azure-bot-service-4.0).

So, without further delay, let's begin by preparing the prerequisites.

# **Demo Application**

This article series includes a demo application called "**ODC with Bots for Teams Demo**," available on **ODC Forge**. Be sure to download the version of the application that matches each article in this series.

For this article, you need to install Version 0.5 from ODC Forge.

* In the ODC Portal, go to **Forge - All Assets**.
    
* Search for "**ODC with Bots for Teams Demo**".
    
* Click on the Asset **(Do not click on Install on the tile!)**.
    
* Switch to the **Version** tab and click on Install next to **Version 0.5**.
    

Version 0.5 depends on other Forge components:

* **OAuthTokenExchange** - An external logic library that helps retrieve access tokens easily.
    
* **Bot Framework Service API** - A connector library for using Bot Connector API endpoints.
    
* **LiquidFluid** - An external logic library used to render handlebar templates based on the Shopify Liquid Templating syntax.
    

# Microsoft Entra Application Registration

In Entra Application Registration, we will configure **permissions**, **scope**, and **authorized clients**, an additional **client secret** and a **redirect URI**.

Our demo bot needs to send an email on behalf of the logged-in user. To send emails, it requires the **Mail.Send** permission.

Additionally, we need to create a **custom scope** and add the **Microsoft Teams client application** as an authorized client. This ensures that only these trusted clients can request tokens from Entra on behalf of the user, which will be used in our bot to send an email.

We create a new **client secret** to be used by the Bot Framework token service to acquire the user token during authentication. You already have one secret configured to retrieve an access token for interacting with the Connector API. You could use the same secret, but creating a separate one makes the purpose of each client secret clearer.

Finally, we add a **redirect URI** to our application registration. This is where the authorization code will be sent after a user successfully signs in from Teams. The entire authentication process is managed by the Azure Bot Framework Service, specifically the **Token Service**, and the redirect URI will direct to that service.

In Azure Portal switch to the Entra application registration that is associated with your configured bot.

## Grant Send Mail Permission

Under the Manage menu, select API permissions.

* Click **Add a permission**.
    
* In the **Request API permissions** sidebar, select **Microsoft Graph**.
    
* Choose **Delegated Permissions** (Permissions on behalf of a user).
    
* Type **Mail.Send** in the search box and check the box next to Mail.Send (Send mail as a user).
    
* Click **Add permissions**.
    

Repeat this step to add the following additional permissions

* **openid**
    
* **email**
    
* **profile**
    

Finally click on **Grant admin consent for &lt;your domain&gt;**.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">By granting admin consent, users won't be asked to approve permissions. This makes the process of performing a silent token exchange simpler.</div>
</div>

## **Configure Scope**

Under the **Manage** menu, select **Expose an API**.

* Click the Add link next to **Application ID URI**.
    
* For the value, enter `api://<your Microsoft Tenant FQDN>/botid-<copied Application (client) ID>`.
    
* Click Save.
    

In the **Scopes defined by this API** section, click on **Add a scope**.

* **Scope name** - access\_as\_user
    
* **Who can consent** - Admins and users
    
* **Admin consent display name** - Allow Teams to access the user profile and use the bot on behalf of the user
    
* **Admin consent description** - Allow Microsoft Teams to access the user profile and call the application's API on behalf of the user
    
* **User consent display name** - Allow Teams to access the user profile and use the bot on behalf of the user
    
* **User consent description** - Allow Microsoft Teams to access the user profile and call the application's API on behalf of the user
    

## Add Authorized Client Applications

Still in the same menu.

In the **Authorized client applications** section, click on **Add a client application**.

Each Microsoft M365 application, such as Teams, Teams for Web, and Outlook, has a unique client ID. We are now allowing the Teams Desktop and Web applications to access our application API (ODC Exposed REST API) on behalf of the logged-in user. You can find a complete list of Microsoft First-Party apps with their identifiers here: [**Verify first-party Microsoft applications in sign-in reports | Microsoft Learn**](https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in).

For the Microsoft Teams **Desktop and Mobile** applications:

* **Client ID** - 1fec8e78-bce4-4aaf-ab1b-5451cc387264
    
* Select the checkbox next to the scope you created earlier
    
* Click **Add application**
    

For the Microsoft Teams for **Web** application:

* **Client ID** - 5e3ce6c0-2b1f-4285-8d4b-75ee78787346
    
* Select the checkbox next to the scope you created earlier
    
* Click **Add application**
    

## **Add Client Secret**

Under the **Manage** menu select **Certificates & secrets**

Select the **Client secrets** tab and click on **New client secret**

* **Description** - Bot User Authentication
    
* **Expires** - Recommended: 180 days (6 months)
    
* Click **Add**
    

Immediately after adding the new client secret, copy the **Value** and save it for later. It will only be displayed once.

## **Redirect URI**

The final configuration step is to add a Redirect URI to the application registration.

Under the **Manage** menu select **Authentication**.

In the Platform configurations section click Add a platform

* Select **Web**
    
* **Redirect URIs** - https://token.botframework.com/.auth/web/redirect
    
* Click **Configure**
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The redirect URI above is the default for bots without data residency requirements. Microsoft provides additional redirect URIs for different regions. For more details, see <a target="_self" rel="noopener noreferrer nofollow" href="https://learn.microsoft.com/en-us/azure/bot-service/ref-oauth-redirect-urls?view=azure-bot-service-4.0" style="pointer-events: none"><strong>Supported OAuth URLs - Bot Service | Microsoft Learn</strong></a>.</div>
</div>

With our Entra application registration set up, we can now add some configuration details to our Azure Bot resource.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Before you leave this section, ensure you have the application registration settings for the <strong>Application (client) ID</strong>, the <strong>Application ID URI</strong> (from Expose an API) and the <strong>client secret</strong>.</div>
</div>

# Azure Bot Resource

Under the Settings menu select Configuration

* Click **Add OAuth Connection Settings**
    
* **Name**: Default
    
* **Service Provider**: Azure Active Directory v2
    
* **Client id**: &lt;Application (client) ID&gt; from Entra application registration
    
* **Client secret**: &lt;Client secret you created you in the previous step&gt;
    
* **Token Exchange URL**: leave empty
    
* **Tenant ID**: common
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">We use "common" here instead of our Directory (tenant) ID from the application registration because we have set up a multi-tenant bot. For single-tenant bots, you would enter the specific tenant ID.</div>
</div>

* **Scopes**: Mail.Send openid email profile
    
* Click **Save**
    

After saving the connection, click on the newly created connection again. In the top right corner of the sidebar, click on Test connection. Complete the sign-in dialog and in the next dialog, you will receive the issued token. You can copy the value and inspect the token at [jwt.io](https://jwt.io).

# App Manifest

The configuration above lets us require users to authenticate through Azure Entra. With Microsoft Teams, we can use a "silent" token exchange feature that swaps a user's Teams access token for an access token issued for the application registration linked to our bot. To enable this "silent" exchange, we need to modify our Teams app manifest and add some extra properties.

```json
{
  ...,
  "permissions": ["messageTeamMembers", "identity"],
  "validDomains": ["token.botframework.com"],
  "webApplicationInfo": {
    "id": "<Application (client) ID>",
    "resource": "api://<domain name>/BotId-<Application (client) ID>"
}
```

* **permissions - identity**: This permission allows the bot to access user identity information. It lets the bot get user details, which is important for personalized interactions and implementing SSO.
    
* **validDomains**: The domain token.botframework.com is included to let the bot use the Bot Framework OAuth flow for authentication. By specifying this domain, the bot can securely interact with the OAuth token service provided by the Bot Framework, which is necessary for handling user authentication and authorization.
    
* **webApplicationInfo - id**: This is the **Application (client) ID** of the bot registered in Microsoft Entra ID. It uniquely identifies the bot application and is used during the authentication process to request tokens.
    
* **webApplicationInfo - resource**: This is the Application ID URI that represents the bot's API. It specifies the intended audience for the authentication tokens.
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The<strong> ODC with Bots for Teams</strong> demo application includes an updated manifest template in <strong>Data - Resources</strong>. After you modify it, reupload the app to Teams.</div>
</div>

After you modify the app manifest with the new values, package it, and reupload the Teams app to Microsoft Teams as described [here](https://without.systems/odc-with-bots-for-teams-application-manifest#heading-create-the-app-manifest-file).

With our prerequisites complete, we can now explore the implementation details. Open ODC Studio and the demo application.

# Bot Handler

This version of the demo application includes a new handler called **BotHandlerSimpleAuthentication**, which is set up as the in-app event handler for the **OnBotActivity** event. In the **Logic** tab, open **Bots - BotHandlerSimpleAuthentication**.

Compared to our previous event handler we made the following additions.

## TryGetUserToken

This action checks the TokenStore to see if there is already a valid, non-expired access token available for the sender of the message and returns it.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1739857785521/0dcb4373-7b5e-4b44-8a48-4056d3695201.png align="center")

## SendOAuthPrompt

Back in the main flow and if there is no non-expired access token available for the user our flow prepares an OAuthCard message and sends it to the user.

An [OAuthCard](https://learn.microsoft.com/en-us/javascript/api/botframework-schema/oauthcard?view=botbuilder-ts-latest) is used to facilitate OAuth authentication. You will find the OAuthCard template under **Data - Resources - Cards**.

```json
{
    "type": "message",
    "attachments": [
        {
            "contentType": "application/vnd.microsoft.card.oauth",
            "content": {
                "connectionName": "{{connectionName}}",
                "tokenExchangeResource": {
                    "id": "{{activity}}"
                }
            }
        }
    ],
    "recipient": {
        "id": "{{recipient}}"
    }
}
```

**RenderOAuthCard** replaces the placeholder values with actual data. It uses the **LiquidFluid** external logic component to create the final card message.

* **connectionName** - The configured OAuth connection name in the Azure Bot resource (Default).
    
* **activity** - The Activity ID of the last activity that triggered the authentication flow. We will discuss why this value is important later. For now, remember that we use this ID to track the last message a user sent in the conversation.
    
* **recipient** - The ID of the recipient.
    

The rendered OAuthCard is then sent to the conversation just like any other message.

## Summary

In our bot handler, we first try to get a valid access token for the user from the TokenStore entity. If the user doesn't have an access token, or if the access token has expired, we send an OAuthCard to the user in the Teams conversation to trigger a token exchange.

# Messaging Endpoint

As soon as Microsoft Teams receives the **OAuthCard** message, it sends an **Activity** of type **invoke** to the bot's configured messaging endpoint. This Activity contains the user's Microsoft Teams client access token, which we need to exchange for a Bot application token using the **Azure Bot Token API**.

Open the **Messages** endpoint in **Logic - Integrations - REST - MessagingEndpoint**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1739860211252/77eaf458-35ac-4e3b-acbb-46a5b1e64249.png align="center")

In the Messages endpoint we handle this special activity of type **invoke** and a name of **signin/tokenExchange**.

## DeserializeInboundTokenExchange

This action deserializes the request in to a **InvokeTokenExchange** structure, a format the Azure Bot Token API expects to exchange a token.

## TryExchangeUserToken

This action first gets an access token to interact with the **Azure Bot Token API** and then calls the ExchangeToken endpoint using our **InvokeTokenExchange** values. If successful, it saves the access token in the **TokenStore** entity for future use.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1739859556906/632eda46-4750-4d88-99ab-b3f83636b0a8.png align="center")

## Original Message Playback

Now comes the tricky part. The user's original message hasn't been processed because our bot handler started an OAuth prompt flow, so the original message is essentially lost. Fortunately, all inbound activities are stored in the **Activity** entity.

When we created the OAuthCard, we added the original message's Activity Id to the card. This Id is sent back to us as part of the invoke - **signin/tokenExchange** activity.

```json
{
  "name": "signin/tokenExchange",
  "type": "invoke",
  ...
  "value": {
    "id": "<Activity ID>",
    "token": "<Teams client token>",
    "connectionName": "Default"
  },
  ...
}
```

We now use this Activity ID to get the original message from the Activity entity (**GetOriginActivity**), deserialize the payload (**DeserializeOriginActivity**), and trigger an **OnBotActivity** with the values from the original activity.

This method replays the original message, and since we now have a valid access token, the bot handler will process the inbound message.

## Summary

Our Messaging Endpoint manages the token exchange. It gets the Microsoft Teams client access token and swaps it for an access token for our registered Bot application using the Azure Bot Token Service API. Then, it retrieves the original user message from the Activity Store and triggers an OnBotActivity with the original message payload. This step is essential because the original message isn't processed if the user doesn't have a valid access token.

# Send Mail via Graph API

Back in our bot handler (**BotHandlerSimpleAuthentication**) lets review the part when our action flow could successfully retrieve a users access token from the Token Store.

## GetProfile

This action queries the **Me** endpoint of the Microsoft Graph API to retrieve the basic user profile using the user's access token. This step is needed to get the User Principal Name and the email address (in most cases, the UPN will be the same as the primary email address).

For this operation, the **User.Read** permission on the Bot application registration in Entra is required.

## SendGraphEmail

This action sends a basic email using the Graph API. In our example, the email is sent from the user to themselves, but it's just for demonstration purposes.

For this operation, the **Mail.Send** permission on the Bot application registration in Entra is necessary.

## Summary

To send an email, we use the cached user access token to first get the basic user profile, and then we send an email to that user via the Graph API. This shows how a Microsoft Teams client access token can be successfully exchanged for a bot-specific access token with granted permissions in the Entra application registration.

# Bonus Excerise

After testing your bot implementation, you'll quickly notice some delay between sending a message and receiving a response from your bot. As an additional exercise, add a **typing** activity (an activity of type **typing**) to:

* The messaging endpoint before triggering the **OnBotActivity** event.
    
* The bot handler right after the condition that checks if the activity type is a message.
    

Sending a **typing** activity in a conversation improves the user experience by providing feedback.

# The End of the ODC with Bots for Teams series

This is the final tutorial in my series on ODC with Bots for Microsoft Teams. As you can imagine, there is a lot more to the topic than what we covered in these tutorials, but I hope the series gives you a good start on your journey to turning OutSystems Developer Cloud into a full-fledged, multi-bot environment.

I hope you enjoyed this tutorial and the entire series. For questions and feedback, please leave a comment here.

%%[follow-linkedin-button]
