Skip to Content
DocumentationIntegrationsMicrosoft Teams bot

Microsoft Teams bot

Erato can answer in Microsoft Teams personal chats, group chats and channels. Chats started in Teams are ordinary Erato chats: they appear in the web app and the Teams tab, keep their history, and use the same assistants, models, policies, MCP tools and SharePoint integration as the web app.

Overview

  • Personal chats stream the answer while it is generated.
  • Group chats and channels answer when the bot is @mentioned, reply in the thread and mention the requester. The preceding messages of the conversation are attached as context.
  • Files uploaded in a personal chat, pasted images, and SharePoint/OneDrive files shared in a conversation become chat files.
  • Tool approvals appear as an Adaptive Card; only the person who asked can decide.
  • Background tasks post their result back into the Teams conversation once they finish.
  • /new starts a fresh Erato chat in the current conversation.

In the chat list of the web app and the add-ins, the Source filter shows only the chats started in Teams, or hides them. It covers the bot and the Teams tab.

Users are recognized by their Entra object ID, which Erato records whenever they sign in to the web app or the Teams tab. People who have never opened Erato are asked to do so once.

Branding

The Teams tab, Office task panes and web frontend can share one theme mount. Teams package icon source settings select PNGs from that theme while retaining existing ZIP entry names and manifest URLs. See the shared branding configuration. Installed Teams clients display packaged icons until the app package is updated.

Prerequisites

  • Erato signs users in with Microsoft Entra ID.
  • The Teams app (integrations.ms_office.teams) is enabled, so the bot can be shipped in the same app package as the tab.
  • A messaging endpoint that Microsoft’s cloud can reach. Add-ins and the Teams tab are loaded by the Outlook or Teams client on the user’s device, so they work on an internal-only host. The bot works the other way round: Teams hands every message to Azure Bot Service, which posts it to https://<host>/api/integrations/ms_teams/messages, server to server. That path therefore needs a public hostname with a certificate from a publicly trusted CA, and TLS 1.2 must stay enabled. For an internal-only deployment, publish only this path, for example through a reverse proxy or web application firewall in a DMZ, or Azure Application Gateway over an existing ExpressRoute or VPN connection. Teams has no mode in which the bot fetches messages itself. See Security and network.
  • Outbound HTTPS from Erato to login.microsoftonline.com, login.botframework.com, *.botframework.com, smba.trafficmanager.net, graph.microsoft.com (identity and groups) and *.sharepoint.com (files people attach).
  • A customer administrator who can create resources in an Azure resource group (and register the Microsoft.BotService provider if the subscription never used it), update the Entra app registration and grant consent (Cloud Application Administrator), and deploy the Teams package: Global Administrator in Microsoft 365 admin center’s Integrated apps, or Teams Administrator in Teams admin center.

Setup

The bot uses the deployment’s existing Entra app registration: the same app as the add-ins, the Teams tab and usually the web login. Erato needs no values that only exist after the Azure setup, so the order is:

  1. The Erato operator enables the bot and deploys once.
  2. Customer IT runs one Cloud Shell command from the setup page. It creates or updates everything in Azure and Entra.
  3. Customer IT uploads the Teams package and sends the bot a message.

Nothing has to be handed back to the operator, and Erato is not redeployed.

1. Enable the bot in Erato

[integrations.ms_office.teams.bot] enabled = true # Only when integrations.ms_office.addin.msal_authority does not name the tenant: tenant_id = "TENANT_ID" # Only when Azure reaches Erato on a different, public hostname: messaging_endpoint = "https://teams-bot.example.com"

Everything else has defaults: app_id and sso_app_id come from the add-in’s msal_client_id, oauth_connection_name is graph-sso, and sso_resource is api://<host>/botid-<app_id> for the host the Teams package is downloaded from. Explicit values still win. Erato logs at startup which settings it filled in; the setup page shows their values.

Upgrading an existing bot without single sign-on: single sign-on is now on by default, so the Teams package rendered after the upgrade points webApplicationInfo at the derived sso_resource. The installed package changes only when you upload a new one; users then accept the update, and the OAuth connection must have that token exchange URL. Run the guided setup before uploading, or set sso_enabled = false to keep the package as it was.

The bot authenticates to Microsoft with its app registration’s client secret (app_password). With the default app_id, that is the secret oauth2-proxy already uses for the web login; no new credential is needed. With the Helm chart:

values.yaml
msTeamsBot: # Pass the existing oauth2-proxy client secret to the backend. appPassword: existingSecret: name: erato-secrets key: OAUTH2_PROXY_CLIENT_SECRET # Route the messaging endpoint around oauth2-proxy on the main host ... directRoute: enabled: true ingressFrom: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: ingress-nginx # ... and/or publish only that path on a separate public hostname. publicIngress: enabled: true host: teams-bot.example.com className: nginx-public annotations: nginx.ingress.kubernetes.io/proxy-body-size: 256k tls: enabled: true secretName: teams-bot-tls

If the Teams tab is already installed, increase integrations.ms_office.teams.manifest.version in the same change: the package gains the bot, and Teams only accepts an update with a higher version.

The /office-addin/setup → Teams page now shows the bot section.

2. Guided setup with Azure Cloud Shell

On the deployment’s Teams setup page:

  1. Read Before you start: the messaging endpoint shown there is the one Azure will call. If it is the internal address the page was opened at and Azure cannot reach it, the Erato operator sets messaging_endpoint first.

  2. The Tenant ID is prefilled. Enter the Subscription ID. To create the Azure Bot, also enter an existing Resource group and a name for the new bot; leave both empty if a bot for this deployment’s app registration already exists.

  3. Select Open Azure Cloud Shell, choose PowerShell, and sign in to the tenant. First-time Cloud Shell use can require registering the Microsoft.CloudShell resource provider. Persistent storage is optional.

  4. Select Copy command, paste it into Cloud Shell, and press Enter. The command fetches a specific PowerShell release from erato.chat, checks its SHA-256 checksum, and performs a read-only check. No local installation, download, file upload, or private repository access is needed.

  5. Read the results: PASS means that Azure setting is present; MISSING means a step remains. Access errors and the wrong tenant stop the check. Under Preview and apply changes, copy the preview command. It uses PowerShell’s -WhatIf; nothing is changed.

  6. Review the preview, then copy the separate apply command when ready. -Apply displays the target and proposed changes and asks for confirmation. It:

    • creates a single-tenant Azure Bot (Free tier, global) for the existing app registration, or corrects an existing bot’s messaging endpoint and enables its Teams channel,
    • adds SSO settings to the existing app registration (a v2 access-token setting, the access_as_user scope, Teams client pre-authorization, the Bot Framework redirect and delegated Graph permissions),
    • creates a separate OAuth connection with a dedicated credential that never leaves Azure,
    • grants tenant-wide consent for the delegated Graph permissions. Without the required role, everything else stays applied and the report links the admin consent page. -SkipConsent leaves consent to a separate process.

    It never changes a bot of another tenant, type or app registration, preserves existing settings, and refuses to overwrite a connection with conflicting settings.

View script shows the exact PowerShell source inside the setup page before execution. View command exposes every parameter and the expected checksum. The command contains public deployment identifiers, never credentials. Every check, preview and apply command independently verifies the script before running it. If verification fails, nothing executes; reopen the setup page and contact Erato support if it persists. Do not remove the checksum check to continue.

Without a resource group and bot name, the helper finds the existing bot for the app registration within the selected subscription; it does not search other subscriptions or switch tenants. If several bots use that app ID, enter the resource group and bot name to select one. Deployment details and connection names lists the identifiers. An existing bot configured before this guided setup may have a graph connection; the helper keeps it and creates graph-sso next to it.

The check also reads the tenant’s app management policies and reports them before any change: a cap on secret lifetime (the dedicated OAuth credential then gets that lifetime instead of 365 days), a policy that blocks new client secrets on the app (Apply stops before writing), and Entra’s restriction on Application ID URIs, which exempts apps that issue v2 access tokens: Apply saves that setting on its own before adding the URI. Reading the policies needs Policy.Read.All; without it the check says so and Apply still runs. Errors name the step that failed. Record the credential’s reported key ID and expiry, and rerun the helper before it expires; secret values are never printed. If apply fails after a partial change, the helper identifies the added credential when available. Inspect the connection and credential before retrying. Process environments sharing an app registration sequentially.

For local administration, the same reviewed script runs with PowerShell 7.2+ and Azure CLI. Browser-based Cloud Shell is the default path. -Json produces a sanitized support report. Exit 0 means checks pass, 1 means settings such as admin consent remain, and 2 means the operation stopped or failed. An apply can complete its writes and still exit 1 because consent remains outstanding. The helper never deploys Erato or installs a Teams package.

References: Cloud Shell overview , PowerShell preview and confirmation .

3. Upload the Teams package and test

  1. On the setup page, download the Teams package. In Microsoft 365 admin center, Settings → Integrated apps, upload it as a Teams app (or update the existing Erato app) and deploy it to its users: deployed apps are installed for them and update without asking. Alternatively, upload it in Teams admin center and install it with an app setup policy. Adding a bot or changing single sign-on otherwise requires every user to accept an update.
  2. When the app is installed for someone, the bot greets them in their personal chat. Users Erato does not know yet are asked to open the Erato tab (or the web app, with public_base_url) once, so Erato records their Entra object ID. Send the bot a message.
  3. The setup page’s status shows whether a Teams message reached Erato (recorded in the database, so on any replica) and whether Microsoft Entra accepts Erato’s bot credential. The credential check works right after the Erato deploy, before any Azure Bot exists, and asks Entra at most once a minute per instance. It distinguishes a rejected secret, an app Entra does not find in the tenant, and Entra being unreachable. Select Refresh status after sending.
  4. Verify that sign-in completes without selecting Sign in, subject to the tenant’s consent and Conditional Access requirements. Also test the Teams web client, the tab and the other Office add-ins, then an @mention in a group chat or channel and a file.

Security and network

The messaging endpoint is the only Erato route that must be reachable from the internet. Microsoft does not support restricting it by IP address: there is no published list of Bot Connector addresses, and Teams traffic has been observed outside the AzureBotService service tag. Anyone can also install a single-tenant bot’s package in another tenant. Microsoft’s guidance is that the bot must refuse what it should not process, so the protection is layered:

  1. Erato (the real gate). Before reading the request body, Erato verifies the Bot Connector token: RS256 signature from Microsoft’s Bot Framework key set, a key endorsed for Teams, issuer https://api.botframework.com, audience equal to the bot’s app ID, and its validity period with five minutes of clock skew. Only then does it read at most 256 KB, and the activity must come from the Teams channel, match the token’s serviceurl, and belong to tenant_id. Signing keys are refreshed at most once a minute, and known keys stay valid if Microsoft is unreachable, so forged tokens cannot make Erato flood Microsoft’s endpoints. Attachments are only downloaded over HTTPS from Teams and SharePoint hosts, re-checked on every redirect. Rejections are logged with their reason, never with the token. Users must be known to Erato, and their data is accessed with their own Teams sign-in.
  2. Edge. Route only /api/integrations/ms_teams/messages, preferably on a separate hostname (msTeamsBot.publicIngress); everything else on that host returns 404. Limit request bodies to about 256 KB, keep TLS 1.2 enabled, and apply an overall rate limit rather than a per-IP one, because Microsoft’s sending addresses are shared. An IP allowlist based on the service tag is unsupported and can break without notice; use it in monitoring mode at most. A web application firewall should use a policy for this path only: known false positives are CRS rules 920300 and 931130, and the SQL injection and XSS rules trip on message text such as pasted code. Never log the Authorization header: a captured token can be replayed until it expires.
  3. Cluster. The network policy lets only the ingress controller reach the backend port; only the exact messaging path bypasses oauth2-proxy.
  4. Microsoft 365 and Azure. Teams app permission policies decide who can use the bot. New Azure Bots get the Web Chat and Direct Line channels enabled; Erato refuses their activities, and you can remove them. Do not delete the Azure Bot while keeping the app registration: only one bot can use an app ID.

To verify the gate from outside, without a token, both requests must fail:

curl -i -X POST https://<public host>/api/integrations/ms_teams/messages \ -H 'Content-Type: application/json' -d '{}' # 401 Unauthorized curl -i https://<public host>/api/integrations/ms_teams/messages # 405 Method Not Allowed

Manual setup

Use these steps if your organization manages Azure and Entra through another process, or to understand what the guided setup does.

1. Record the identities and deployment URL

Name used belowMeaning
TENANT_IDCustomer’s Entra tenant
BOT_APP_IDEntra client ID registered as the bot’s messaging identity
AUTH_APP_IDEntra client ID used by the add-in, OAuth connection and bot SSO
TEAMS_APP_IDPackage ID in integrations.ms_office.teams.app_id; keep it stable
ERATO_HOSTPublic Erato hostname, without https:// or a path
SSO_RESOURCEExact Application ID URI exposed by AUTH_APP_ID

For a new deployment, BOT_APP_ID and AUTH_APP_ID can both be the existing customer-owned single-tenant add-in registration. A bot authenticates as a server and needs a client secret even though browser MSAL does not use one.

For an existing deployment with separate bot registrations, retain BOT_APP_ID and its working credentials. The OAuth connection can use AUTH_APP_ID separately. For our combined tab/bot package, keeping AUTH_APP_ID equal to the current tab’s client ID avoids changing its NAA identity. The generated manifest has one webApplicationInfo.id; bots[].botId identifies the messaging bot. Microsoft documents separate bot and authentication registrations .

2. Extend the existing Entra authentication registration

Open Entra admin center → App registrations → the application with AUTH_APP_ID. Keep its existing redirect URIs, scopes, permissions, and credentials.

  1. Under Manifest, set api.requestedAccessTokenVersion to 2. This concerns access tokens issued for this application’s exposed API; review existing API consumers if the registration already exposes one.
  2. Under Expose an API, configure the Application ID URI. For a combined bot/tab app, use api://ERATO_HOST/botid-BOT_APP_ID. Record this as SSO_RESOURCE without /access_as_user at the end. The suffix must use the messaging bot’s BOT_APP_ID, even when AUTH_APP_ID is a separate registration. Teams can otherwise report resourcematchfailed and show no reply.
  3. Add an enabled delegated scope named access_as_user. Use clear admin/user consent labels such as “Access Erato as the signed-in user”.
  4. Under Authorized client applications, add both clients below and select access_as_user for each.
ClientApplication ID
Teams desktop and mobile1fec8e78-bce4-4aaf-ab1b-5451cc387264
Teams web5e3ce6c0-2b1f-4285-8d4b-75ee78787346
  1. Under Authentication → Add a platform → Web, add https://token.botframework.com/.auth/web/redirect. Keep the existing Web callbacks and SPA brk-multihub://ERATO_HOST redirect.

  2. Under API permissions → Microsoft Graph → Delegated permissions, include the following permissions, together with any additional scopes Erato uses:

    openid profile email offline_access User.Read GroupMember.Read.All Files.Read.All Sites.Read.All Chat.Read ChannelMessage.Read.All
  3. Have an appropriately privileged tenant administrator grant admin consent. Configuring permissions alone does not grant consent. Consent on an old bot registration does not transfer to the shared authentication registration.

  4. Under Certificates & secrets, add a dedicated OAuth connection credential. Use its Value, not its Secret ID, in step 4. Keep existing credentials.

If staging and production share an authentication registration, retain both environment-specific resource URIs in its identifierUris array (the Entra manifest editor or Graph API can edit this array). Use the same access_as_user scope and Teams client pre-authorizations. Process the environments sequentially.

References: Entra SSO configuration , combined bot/tab manifest , Application ID URI policy .

3. Create or inspect the Azure Bot resource

Open Azure Portal → All resources and find the existing Azure Bot. If none exists, create an Azure Bot in the customer’s subscription/resource group, select Single Tenant → Use existing app registration, and supply BOT_APP_ID and TENANT_ID. Record the resource name, subscription and resource group.

Under Configuration, set the messaging endpoint to the publicly reachable address (the messaging_endpoint host when Erato itself is internal-only):

https://ERATO_HOST/api/integrations/ms_teams/messages

Under Channels, enable Microsoft Teams. The bot credential corresponding to BOT_APP_ID belongs in the Erato deployment’s secret storage. This is separate from the OAuth credential when BOT_APP_ID and AUTH_APP_ID differ.

4. Configure the OAuth connection

On the Azure Bot resource, open Configuration → OAuth Connection Settings. For a staged rollout, create a new connection named graph-sso and keep any existing graph connection until validation is complete.

If helper version 1.0.0 configured graph-sso with an authentication-app-ID URI, the current setup command previews and repairs that specific mismatch. It keeps the existing credential and old Entra URIs, and refuses the repair if the connection’s authentication app, tenant, provider, or required permissions differ. After applying, update Erato’s sso_resource and the deployed Teams package as described below; changing Azure alone does not update the installed manifest.

FieldValue
Namegraph-sso
Service providerAzure Active Directory v2
Client IDAUTH_APP_ID
Client secretThe dedicated credential value from step 2
Tenant IDTENANT_ID
Token Exchange URLSSO_RESOURCE, exactly as exposed in Entra
ScopesThe space-separated delegated Graph scopes listed in step 2

Save and use Test Connection. A successful test verifies interactive OAuth; it does not establish that Teams silent SSO works. That requires the package and client checks below. See Microsoft’s OAuth connection configuration .

5. Configure the Erato deployment

With BOT_APP_ID and AUTH_APP_ID both equal to the add-in’s msal_client_id, the connection named graph-sso and SSO_RESOURCE equal to api://ERATO_HOST/botid-BOT_APP_ID, the defaults from step 1 of the guided setup already match. Otherwise set the values that differ:

[integrations.ms_office.teams.bot] enabled = true app_id = "BOT_APP_ID" tenant_id = "TENANT_ID" oauth_connection_name = "graph-sso" sso_app_id = "AUTH_APP_ID" sso_resource = "SSO_RESOURCE" # Supply app_password through the deployment's secret storage.

Keep the add-in’s msal_client_id = "AUTH_APP_ID". Keep TEAMS_APP_ID and the existing bot identity stable. Increase the Teams manifest version so the updated package is distinguishable from the installed version.

The exact path /api/integrations/ms_teams/messages must reach the backend without oauth2-proxy stripping its Bot Connector Authorization header. The Erato Helm chart supports msTeamsBot.directRoute; configure the ingress/network-policy allowance for the actual controller. For example:

values.yaml
msTeamsBot: directRoute: enabled: true ingressFrom: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: ingress-nginx

The backend validates this route’s authentication.

Apply the deployment using the environment’s normal deployment procedure. The Cloud Shell helper does not modify Helm, Kubernetes, or Erato configuration.

6. Update the Teams package and verify the user experience

  1. Open https://ERATO_HOST/office-addin/setup, select Teams, and download the package. Inspect manifest.json inside the ZIP:
    • bots[0].botId is BOT_APP_ID.
    • webApplicationInfo.id is AUTH_APP_ID.
    • webApplicationInfo.resource is exactly SSO_RESOURCE.
    • nestedAppAuthInfo retains the tab’s NAA redirect.
    • validDomains includes exactly token.botframework.com.
  2. Upload the package as an update to the existing app through the customer’s Teams/Microsoft 365 app-management process. Downloading a new manifest does not update the installed app.
  3. Open the Erato web app or Teams tab once with the test user to record their Entra object ID. Then test a fresh personal bot conversation.
  4. Verify that user authentication completes without clicking Sign in, subject to the tenant’s consent and Conditional Access requirements. Also test the Teams web client, existing tab login, and the other Office add-ins.
  5. Test an @mention and a file operation after the personal-chat check succeeds.

The bot resumes the original message after successful silent or interactive sign-in. Teams can hide the OAuth card when resource matching fails, leaving the conversation silent. For resourcematchfailed, verify both exact URI agreement and that its botid- suffix uses the messaging bot’s ID. Distribute an updated Teams package after changing the manifest. Channels use a personal conversation for authentication.

If necessary, roll back the deployment to oauth_connection_name = "graph", remove the SSO overrides, and distribute a higher-version package with the prior authentication manifest. Keep the original connection and credentials during rollout. Additive changes to a shared Entra app also affect the other environments using it; remove only additions whose consumers you have checked.

Configuration Options

OptionTypeDefaultDescription
enabledbooleanfalseWhether the Teams bot is enabled
app_idstringadd-in msal_client_idBot app registration ID
app_passwordstringBot client secret; with the default app_id, the web login’s (oauth2-proxy) secret
tenant_idstringtenant of a tenant-specific msal_authorityEntra tenant whose activities are accepted
oauth_connection_namestring"graph-sso"Azure Bot OAuth connection that yields the user’s Graph token
token_service_urlstring"https://token.botframework.com"Bot Framework token service
sso_enabledbooleantrueTeams single sign-on; false keeps an existing bot without SSO and its manifest unchanged
sso_app_idstringadd-in msal_client_idApp ID for silent sign-in, rendered into webApplicationInfo.id
sso_resourcestringapi://<host>/botid-<app_id>Token exchange URL for silent sign-in; <host> is where the Teams package is downloaded from
public_base_urlstringAddress users open Erato at, for links in bot messages; an internal host is fine
messaging_endpointstringsetup page addressPublic URL Azure Bot Service posts messages to, shown on the setup page and passed to its helper
assistant_idstringAssistant that new Teams chats are bound to
context_message_countinteger20Preceding group chat or channel messages attached as context (0 disables)
streamingbooleantrueStream answers in personal chats
security_groups_onlybooleantrueResolve only security groups; match the web login’s groups claim setup

Limitations

  • MCP servers that forward the user’s OIDC ID token receive none on Teams requests; servers using access-token forwarding get the Graph token.
  • Client tools that run in the desktop sidecar are not available in Teams. The answer links to the chat in Erato instead.
  • In group chats and channels, answers built from the requester’s data are visible to everyone in the conversation.
Last updated on