How Authentication Works
botas handles bot authentication automatically using a two-auth model. You don't need to write any auth code — just provide three credentials (CLIENT_ID, CLIENT_SECRET, TENANT_ID) and botas takes care of the rest.
This guide explains how authentication works under the hood. If you just want to get credentials and start coding, see the Setup Guide.
The Two-Auth Model
Every Teams bot needs to authenticate in both directions:
| Direction | What happens | How it works |
|---|---|---|
| Inbound | Bot Service sends an activity to your bot | Your bot validates the JWT bearer token on every POST /api/messages request. Invalid tokens are rejected with a 401 before any of your code runs. |
| Outbound | Your bot replies to the user | botas acquires an OAuth2 client-credentials token and attaches it to every call to the Bot Service REST API. Token caching and refresh are handled automatically. |
This two-way security model ensures:
- Only the Bot Service can send activities to your bot (inbound JWT validation)
- Only your bot can send messages on behalf of your app (outbound client credentials)
How Inbound Auth Works (JWT Validation)
When the Bot Service sends a message to your bot, it includes a signed JWT (JSON Web Token) in the Authorization header.
botas automatically:
- Fetches signing keys from the Bot Service's OpenID metadata endpoint (
https://login.botframework.com/v1/.well-known/openidconfiguration) - Verifies the JWT signature using those keys
- Checks the token claims:
aud(audience) must match yourCLIENT_IDiss(issuer) must behttps://api.botframework.comexp(expiry) must be in the future
If any of these checks fail, the request is rejected with 401 Unauthorized before your handlers ever see it.
You never see invalid requests
botas middleware runs before your code. If JWT validation fails, your handlers are never called — the request is rejected immediately.
How Outbound Auth Works (Client Credentials)
When your bot sends a reply, botas needs to prove to the Bot Service API that the request is authorized.
botas automatically:
- Acquires an OAuth2 token using the client credentials flow
- Scope:
https://api.botframework.com/.default - Credentials: Your
CLIENT_ID,CLIENT_SECRET, andTENANT_ID
- Scope:
- Caches the token in memory until it expires
- Attaches the token to every outbound API call (
Authorization: Bearer <token>) - Automatically refreshes when the token expires
The TokenManager component handles this entire flow. You never interact with it directly.
.NET Azure AD Configuration
.NET samples use the Microsoft.Identity.Web library for OAuth2 token acquisition. You can configure credentials in appsettings.json or override with environment variables in launchSettings.json.
Configure MSAL with the AzureAd configuration schema
{
"$schema": "https://json.schemastore.org/appsettings.json",
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-bot-app-id>",
"ClientCredentials": [
{
"SourceType": "ClientSecret",
"ClientSecret": "<your-client-secret>"
}
]
}
}{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"local": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:3978",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"AzureAd__Instance": "https://login.microsoftonline.com/",
"AzureAd__TenantId": "<your-tenant-id>",
"AzureAd__ClientId": "<your-bot-app-id>",
"AzureAd__ClientCredentials__0__SourceType": "ClientSecret",
"AzureAd__ClientCredentials__0__ClientSecret": "<your-client-secret>"
}
}
}
}AzureAd__Instance=https://login.microsoftonline.com/
AzureAd__ClientId="<your-bot-app-id>"
AzureAd__TenantId="<your-tenant-id>"
AzureAd__ClientCredentials__0__SourceType="ClientSecret"
AzureAd__ClientCredentials__0__ClientSecret="<your-client-secret>"The env-to-launch-settings.mjs helper script converts a .env file to launchSettings.json format automatically.
Troubleshooting Auth Failures
401 Unauthorized on incoming messages
Cause: JWT validation failed. Most commonly, the CLIENT_ID in your .env doesn't match the app registration.
Fix:
- Verify
CLIENT_IDmatches the Application (client) ID from your app registration - Check that your bot's messaging endpoint is configured correctly
- Ensure your tunnel is running and the URL hasn't changed
403 Forbidden on outbound replies
Cause: Token acquisition failed or the token was rejected by the Bot Service API.
Fix:
- Verify
CLIENT_SECRETis correct and hasn't expired - Regenerate the secret if needed:
teams app secret reset <appId> - Update your
.envwith the new secret and restart your bot
Bot doesn't respond at all
Cause: The Bot Service can't reach your bot's messaging endpoint.
Fix:
- Check that your dev tunnel is running
- Verify the messaging endpoint URL is correct (should end with
/api/messages) - Test your tunnel:
curl https://<tunnel-url>/api/messagesshould return a response (even if it's an error)
Learn More
- Setup walkthrough: Setup Guide — step-by-step instructions from zero to working bot
- Code examples: Getting Started — echo bots in all three languages
- Architecture details: specs/architecture.md — technical design and component diagram
- OAuth2 flows: Microsoft identity platform docs — deep dive on Azure AD authentication