Microsoft Teams integration¶
A Microsoft Teams integration can be added to your Drovio Server installation. It lets users start and join Drovio sessions from within Microsoft Teams, using the compose box or bot commands. It comes as a JAR file that you plug into Drovio Server and configure. You will need to create an Azure Bot and an app in Microsoft Entra ID.
Register the bot in Azure¶
The bot registration can be done from the Azure portal, the Azure CLI, or Terraform. All three produce the same result: an app registration, a client secret, and an Azure Bot resource with the Teams channel enabled.
Create the Azure Bot resource¶
In the Azure portal, Create a resource, find Azure Bot, then Create.
| Field | Value |
|---|---|
| Bot handle | A unique name, for instance Drovio |
| Subscription | The subscription to bill |
| Resource group | A new group, for instance Drovio |
| Pricing tier | Free (F0) |
| Type of app | Single Tenant |
| Creation type | Create new Microsoft App ID |
Point the bot at your server¶
Open the resource, then Settings and Configuration. Set the Messaging endpoint to:
https://<your server>/msteams/api/messages
Public endpoint
The /msteams/api/messages route must be reachable from Microsoft's
servers over HTTPS. If your Drovio Server is behind a firewall or a
reverse proxy, make sure this path is open to the internet.
Apply, then go to Settings and Channels, and add Microsoft Teams.
Collect the three values the plugin needs¶
Still under Settings and Configuration, click Manage Password next to the Microsoft App ID. This opens the app registration in Microsoft Entra ID.
On the Overview pane, record:
| Value in Azure | Used as |
|---|---|
| Application (client) ID | client_id |
| Directory (tenant) ID | tenant_id |
Then go to Certificates & secrets, tab Client secrets, and click
New client secret. Copy the Value column straight away: it is displayed
once, and a new secret has to be generated if you miss it. This is the
client_secret.
Expose the API and grant access to Graph¶
Under Expose an API, click Set next to the Application ID URI, then Save.
Under API permissions, add a permission on Microsoft Graph, choose Application permissions, and select the two permissions listed below. Grant admin consent for them.
Register the redirect URI¶
Only needed if you intend to publish the app from the administration panel rather than uploading it yourself.
Under Authentication, Add a platform, choose Web, and add the redirect URI:
https://<your server>/admin/access/msteams/publish/callback
Publishing fails with AADSTS500113: No reply address is registered for the
application when it is missing. If administration access is restricted by IP
address on this server, the address the browser comes back from has to be
allowed as well, otherwise the return leg is refused with a 403.
Replace <your server> with the public URL of your instance, and pick your
own resource group and region.
The app comes before the bot here
The portal creates the app registration and the bot in one form. The CLI
cannot: az bot create takes the application ID as a required argument,
so the app registration has to exist first. The end result is the same.
Set the five names first, then the rest of the commands can be pasted as they are:
| Variable | What it names |
|---|---|
SERVER |
Public URL of your Drovio Server |
APP_NAME |
Display name of the app registration |
BOT_NAME |
The bot resource, which has to be free across Azure |
GROUP |
Resource group holding the bot |
LOCATION |
Region of the resource group |
Create the app registration, its service principal, its Application ID URI and its client secret:
APP_ID=$(az ad app create \
--display-name "$APP_NAME" \
--sign-in-audience AzureADMyOrg \
--web-redirect-uris "$SERVER/admin/access/msteams/publish/callback" \
--query appId --output tsv)
TENANT_ID=$(az account show --query tenantId --output tsv)
az ad sp create --id "$APP_ID"
az ad app update --id "$APP_ID" --identifier-uris "api://$APP_ID"
CLIENT_SECRET=$(az ad app credential reset --id "$APP_ID" --append \
--display-name drovio-server --query password --output tsv)
The az ad sp create line is the step the portal hides from you. It creates
the service principal the permissions are granted to and admin consent fails
without it. The identifier URI is what the Teams manifest points at. The
client secret is displayed once, make sure to keep what the last command captures.
Ask for the two Graph permissions, resolved by name so no identifier has to be copied around. Consenting to them comes later, at the end of this tab:
GRAPH_ID=00000003-0000-0000-c000-000000000000
USER_READ=$(az ad sp show --id $GRAPH_ID \
--query "appRoles[?value=='User.Read.All'].id | [0]" --output tsv)
CATALOG_READ=$(az ad sp show --id $GRAPH_ID \
--query "appRoles[?value=='AppCatalog.Read.All'].id | [0]" --output tsv)
az ad app permission add --id "$APP_ID" --api $GRAPH_ID \
--api-permissions "$USER_READ=Role" "$CATALOG_READ=Role"
This command prints a hint inviting you to run az ad app permission grant:
ignore it, that command covers delegated permissions, and these two are
application permissions.
Then the bot resource itself, and the Teams channel on it:
az group create --name "$GROUP" --location "$LOCATION"
az bot create \
--name "$BOT_NAME" \
--resource-group "$GROUP" \
--app-type SingleTenant \
--appid "$APP_ID" \
--tenant-id "$TENANT_ID" \
--endpoint "$SERVER/msteams/api/messages" \
--sku F0
az bot msteams create --name "$BOT_NAME" --resource-group "$GROUP"
echo "client_id $APP_ID (Application ID)"
echo "tenant_id $TENANT_ID (Directory ID)"
echo "client_secret $CLIENT_SECRET"
Grant the consent last¶
Consenting is the final step, and it has to be verified. Microsoft Entra ID needs the app registration, its service principal and its requested permissions to have propagated before it can act on them. Run too early, the command reports no error and grants nothing:
az ad app permission admin-consent --id "$APP_ID"
SP_ID=$(az ad sp show --id "$APP_ID" --query id --output tsv)
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/servicePrincipals/$SP_ID/appRoleAssignments" \
--query "length(value)" --output tsv
The last command has to print 2. If it prints 0, wait a minute and run
both again. Granting the consent needs the Privileged Role Administrator or
the Global Administrator role.
The portal is the reliable fallback
If the count stays at 0, open the app registration in Microsoft Entra
ID, go to API permissions and click Grant admin consent for
<your tenant>. The Status column then reads Granted, and the
command above prints 2.
The same setup, for an infrastructure already described as code. It needs the
azuread provider in version 3 or above, which is where the small
composable application resources appeared. The files below form one root
module.
resource "azuread_application_registration" "drovio" {
display_name = "Drovio"
sign_in_audience = "AzureADMyOrg"
}
resource "azuread_application_identifier_uri" "drovio" {
application_id = azuread_application_registration.drovio.id
identifier_uri = "api://${azuread_application_registration.drovio.client_id}"
}
resource "azuread_application_redirect_uris" "drovio" {
application_id = azuread_application_registration.drovio.id
type = "Web"
redirect_uris = ["${var.server_url}/admin/access/msteams/publish/callback"]
}
resource "azuread_application_api_access" "drovio" {
application_id = azuread_application_registration.drovio.id
api_client_id = data.azuread_application_published_app_ids.well_known.result["MicrosoftGraph"]
role_ids = [
data.azuread_service_principal.msgraph.app_role_ids["User.Read.All"],
data.azuread_service_principal.msgraph.app_role_ids["AppCatalog.Read.All"],
]
}
resource "azuread_application_password" "drovio" {
application_id = azuread_application_registration.drovio.id
}
resource "azuread_service_principal" "drovio" {
client_id = azuread_application_registration.drovio.client_id
}
# The two assignments below are the admin consent
resource "azuread_app_role_assignment" "user_read_all" {
app_role_id = data.azuread_service_principal.msgraph.app_role_ids["User.Read.All"]
principal_object_id = azuread_service_principal.drovio.object_id
resource_object_id = data.azuread_service_principal.msgraph.object_id
}
resource "azuread_app_role_assignment" "app_catalog_read_all" {
app_role_id = data.azuread_service_principal.msgraph.app_role_ids["AppCatalog.Read.All"]
principal_object_id = azuread_service_principal.drovio.object_id
resource_object_id = data.azuread_service_principal.msgraph.object_id
}
resource "azurerm_resource_group" "drovio" {
name = "Drovio"
location = "West Europe"
}
resource "azurerm_bot_service_azure_bot" "drovio" {
name = "Drovio"
resource_group_name = azurerm_resource_group.drovio.name
location = "global"
sku = "F0"
microsoft_app_id = azuread_application_registration.drovio.client_id
microsoft_app_type = "SingleTenant"
microsoft_app_tenant_id = data.azuread_client_config.current.tenant_id
endpoint = "${var.server_url}/msteams/api/messages"
}
resource "azurerm_bot_channel_ms_teams" "drovio" {
bot_name = azurerm_bot_service_azure_bot.drovio.name
location = azurerm_bot_service_azure_bot.drovio.location
resource_group_name = azurerm_resource_group.drovio.name
}
output "client_id" {
description = "Application (client) ID, for the plugin configuration"
value = azuread_application_registration.drovio.client_id
}
output "tenant_id" {
description = "Directory (tenant) ID, for the plugin configuration"
value = data.azuread_client_config.current.tenant_id
}
output "client_secret" {
description = "Client secret, for the plugin configuration"
value = azuread_application_password.drovio.value
sensitive = true
}
Three things to know before applying this:
- The client secret ends up in the Terraform state, which then holds a
credential to your tenant and has to be protected as such. Drop the
azuread_application_passwordresource and create the secret by hand if that is not acceptable where you keep your state. - The two
azuread_app_role_assignmentresources are what Grant admin consent does in the portal. The identity running Terraform needs the Privileged Role Administrator or the Global Administrator role to create them. azurerm_bot_service_azure_botis the Azure Bot resource. Don't use the olderazurerm_bot_channels_registration, it describes the previous generation of bot registration.
A single-tenant bot serves your own tenant, which is what a self-hosted deployment needs. Multi-tenant distribution across customer tenants is a separate matter, handled through AppSource.
Whichever method you use, the bot name is between 4 and 42 characters and has to
be free across Azure, so Drovio alone may be taken. The bot resource itself
lives at the global location, whatever the region of its resource group.
The client secret expires
The same secret authenticates the bot when it answers in Teams and the plugin when it calls Graph. Once it expires, the app stops responding and the panel loses sight of the catalog until a new secret is entered in the administration panel. How long it lasts depends on how you created it:
| Method | Lifetime |
|---|---|
| Portal | Your choice, 24 months at most, and Microsoft advises under 12 |
| Azure CLI | One year, --years on az ad app credential reset changes it |
| Terraform | Two years, the default Microsoft Entra ID applies when end_date_relative is left out |
Note the expiry date somewhere and plan the rotation: create the new secret first then paste it into the panel.
Free vs Standard
The Free tier (F0) allows 10,000 messages per month on premium channels, which is enough for most self-hosted deployments. Switch to Standard (S1) if you expect higher volumes.
Graph permissions¶
| Permission | What it buys | Without it |
|---|---|---|
User.Read.All |
Names and pictures of the participants | The integration works, cards show no picture |
AppCatalog.Read.All |
The panel reads the app catalog of your organization | The panel says nothing about the catalog, and its button offers publishing or updating without knowing which |
Both are Application permissions, and both need admin consent. The plugin asks for a fresh token every time it reads the catalog, so a consent granted while the server runs applies to the next read, with no restart to perform.
Publishing needs a third permission, the delegated AppCatalog.ReadWrite.All
and it works differently. It is not declared on the app registration: the
administration panel asks for it when you try to publish the app.
Install the plugin¶
Copy drovio-teams.jar into the packages folder of the server, then declare it
in settings.conf:
"plugin_management": {
"plugins": [
{
"classpath": "/opt/drovio-server/packages/drovio-teams.jar",
"path": "com.drovio.server.teams",
"ext": "java",
"base_address": "drovio.server.msteams"
}
]
}
The key is plugin_management. Writing plugin_manager gives a plugin that
never loads, and no error says so. The base_address is
drovio.server.msteams, which the Drovio application and the server both
address in code.
Restart the server. A Microsoft Teams entry appears in the administration panel, and only appears when the plugin is loaded.
Why a restart here
Most of the Drovio Server configuration is applied live, but plugins are
loaded at startup only. Any change to plugin_management, this plugin
included, takes effect on the next restart.
Configure the integration¶
Open the Microsoft Teams entry of the administration panel.
| Field | Where it comes from |
|---|---|
| Status | Enabled or Disabled |
| Application ID | The client_id recorded in Azure |
| Client secret | The secret you copied |
| Tenant ID | The tenant_id recorded in Azure |
| Launch URI | Prefilled from the server URL |
| Join URI | Prefilled from the server URL |
Saving writes the values into the server configuration and takes effect on the next call, with no restart to perform. The client secret is never displayed again: leaving the field empty keeps the stored one.
Before 3.6.6, changing the Application ID needed a restart
Earlier versions read it once when the server started, so the bot kept
checking incoming calls against the previous one and answered every request
with 401, logging Bot authentication failed: Invalid JWT audience.
Turning the status to Disabled stops the bot and the actions coming from the Drovio application, while leaving this panel available to configure the integration.
Configuration reference¶
These are the keys of the msteams element of settings.conf, should you
prefer to edit it directly.
The plugin writes this element itself on the first startup that finds none, so a server can be deployed with the plugin and configured entirely from the panel.
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Enable the integration. The default if missing is false |
client_id |
Application (client) ID of the bot | |
client_secret |
Client secret of the bot | |
tenant_id |
Directory (tenant) ID. Empty selects a multi-tenant bot (deprecated on Microsoft end as of July 2025) | |
launch_uri |
<server url>/launch |
Where a Teams card sends a user to start a call |
join_uri |
<server url>/join/app |
Where a Teams card sends a user to join a call |
Build and publish the Teams app¶
The app is a zip archive holding the manifest and the icons. The App package section of the admin panel allows to build it.
| Field | Notes |
|---|---|
| Application ID | Prefilled from the configuration |
| Application ID URI | Prefilled with api://<app id>. Change it only if Entra ID exposes another one |
| Version | Shipped with the plugin, and displayed for information. Updating the plugin is what publishes a new version |
Download gives you the archive to upload yourself, through the Teams admin center.
The other button signs you in to Microsoft and puts the app in the catalog of
your organization directly. Its label follows what the catalog holds: Publish
when the app is not there, Update to x.y.z when it holds an older version,
and Up to date when there is nothing to send. Reading that state is what
AppCatalog.Read.All buys. Without it the button reads Publish or update
and covers both cases, which works just as well.
Publishing requires an account holding the Teams administrator role: any other account can only submit the app for review, which then waits for an approval in the Teams admin center. Nothing of that sign-in is kept: publishing again signs in again.
The catalog reflects the app management policies of the tenant which Microsoft applies within 24 to 48 hours of a publication. An app published moments ago may therefore not be listed yet and the panel may keep offering to publish it.
Allow the app and make it easy to find¶
In the Teams admin center, go to Teams apps then Manage apps, search for Drovio, and set its status to Allowed.
The app displays the profile picture of the participants. On the Permissions tab, click Review permissions and grant them.
To put the app in front of your users, go to Teams apps then Setup policies. Edit the Global (Org-wide default) policy, or create one and assign it to a group. Under Pinned apps, add Drovio, and order it under the Messaging extensions scope.
Things to know about pinning policies
- The app opens from the
+of the compose box. The current Teams client no longer shows message extension icons next to the compose box. There is no icon to pin in front of it. The policy sets the order inside the+menu, which is what puts Drovio first. - A policy change takes a few hours to reach the clients, and so does a rollback.
- Leaving User pinning off removes the pins your users made themselves.