MINARA

Microsoft Teams

Bot Framework messaging with JWT + JWKS-validated inbound activities. Multi-tenant or single-tenant.

🟡 Outbound-ready, inbound JWT-verified, talks to Teams via the Bot Connector REST API. Inbound activities are signed by Microsoft and verified against a dynamically-discovered JWKS, so a spoofed POST returns 401.

What you get

  • Outbound text via Bot Framework Conversation API: POST {serviceUrl}/v3/conversations/{conversationId}/activities with {type:"message", text}. Threads (replyToActivityId) are supported.
  • Inbound webhook at /webhooks/teams. Microsoft signs every activity with a JWT; Minara discovers the JWKS via the OpenID metadata endpoint (https://login.botframework.com/v1/.well-known/openidconfiguration) and caches both for 24 hours.
  • OAuth client-credentials token cache for outbound, 1-hour TTL with 5-minute refresh headroom.
  • Text only this PR. Adaptive Cards, Office 365 connectors, and file uploads are a future enhancement.
  • 28000-char text limit.

Setup

1. Register a bot (Azure)

  1. In the Azure portal, create an Azure Bot resource (single region; pricing tier "F0 free" is enough for development)
  2. After provisioning, the "Configuration" blade shows the Microsoft App ID (a GUID) and lets you create a Client Secret (the App Password). Save both.
  3. Decide tenant scope: "Multi Tenant" (use common for the tenant id) or "Single Tenant" (use your Azure AD tenant GUID).

2. Connect the bot to Teams

  1. Same Azure Bot blade → "Channels" → "Microsoft Teams". Accept the Terms of Service and enable.
  2. Create a Teams app manifest (or use the Developer Portal for Teams) that points to your bot's Microsoft App ID. Side-load into your tenant for testing.
  3. Add the bot to a team or DM it from a test user. The first inbound activity carries the conversation.id and serviceUrl Minara needs.

3. Set the bot's messaging endpoint

In the Azure Bot blade → "Configuration" → "Messaging endpoint", set the URL to https://<your-host>/webhooks/teams.

4. Configure Minara

minara auth messaging add
# pick `teams` from the list; paste App ID, App Password, tenant id
# (or "common"), the default conversation id and service URL.

Or set env vars:

TEAMS_BOT_APP_ID=<bot Microsoft App ID GUID>
TEAMS_BOT_APP_PASSWORD=<bot Microsoft App Password>
TEAMS_BOT_TENANT_ID=common
TEAMS_DEFAULT_CONVERSATION_ID=<conversation id from a captured inbound activity>
TEAMS_DEFAULT_SERVICE_URL=https://smba.trafficmanager.net/teams/

The TEAMS_DEFAULT_SERVICE_URL value above is the production multi- tenant route. For single-tenant or sovereign-cloud deployments, override with whatever serviceUrl appeared in the first inbound activity from your tenant.

5. Test

minara auth messaging test teams

Inbound webhook

Bot Framework signs inbound activities with a JWT in the Authorization: Bearer ... header. Minara verifies:

ClaimExpected value
iss (issuer)https://api.botframework.com
aud (audience)TEAMS_BOT_APP_ID (multi-tenant) or TEAMS_BOT_TENANT_ID GUID (single-tenant)
SignatureRS256, key discovered from https://login.botframework.com/v1/.well-known/openidconfiguration
Clock skew±5 minutes

JWKS + OpenID metadata are cached for 24 hours. Microsoft rotates keys periodically; on a kid miss the cache is invalidated and refetched once.

Activity types accepted: message. Other types (conversationUpdate, typing, installationUpdate) are dropped.

Limits & caveats

  • serviceUrl is per-conversation. The Bot Framework documentation says you must capture serviceUrl from each inbound activity and persist it per-conversation. Minara honours the inbound serviceUrl if present; the env default is only the bootstrap fallback for cold push.
  • Multi-tenant audience. With TEAMS_BOT_TENANT_ID=common the bot accepts activities from any tenant. The JWT audience equals TEAMS_BOT_APP_ID. For single-tenant, set the tenant GUID and the audience becomes the tenant GUID.
  • Adaptive Cards. Not yet supported; the bot speaks plain text.
  • JWKS discovery requires network access at first inbound POST. If login.botframework.com is firewalled, validation fails closed (401).

Troubleshooting

"401 on every inbound"

  • Clock skew > 5 minutes between your host and Microsoft.
  • TEAMS_BOT_APP_ID value does not match the bot's Microsoft App ID in Azure (a frequent paste mistake; the Azure Bot has both an Application ID and a resource ID, you need Application ID).
  • For single-tenant: TEAMS_BOT_TENANT_ID does not match the audience in the inbound JWT.

"401 on outbound (token endpoint)"

  • TEAMS_BOT_APP_PASSWORD was rotated or revoked. Generate a fresh Client Secret in the Azure Bot configuration blade.

"Conversation not found (404 on outbound)"

  • The default TEAMS_DEFAULT_CONVERSATION_ID is stale (user uninstalled the bot, or the channel was deleted). Capture a fresh conversation id from a recent inbound activity.

Reference

On this page