Skip to content

[Bug] MS Teams SingleTenant bot: JWT validation fails (issuer + audience mismatch) #64270

@mschaepers

Description

@mschaepers

Description

When using a SingleTenant Azure Bot with the MS Teams channel, incoming webhook requests from Azure Bot Service are rejected with 401 Unauthorized. This affects all new bot deployments since Microsoft deprecated MultiTenant bot creation after 2025-07-31.

Root Cause

Two issues in the JWT validation chain:

1. Issuer mismatch in validateIssuer() (jwt-validator)

The allowedTenantIds issuer check only accepts:

https://login.microsoftonline.com/{tenantId}/

But SingleTenant Bot Framework tokens use the v1 issuer:

https://sts.windows.net/{tenantId}/

2. Audience mismatch in entraValidator (graph-users / msteams extension)

The entraValidator in createBotFrameworkJwtValidator() does not include https://api.botframework.com in its accepted audiences. The default audience list is [clientId, "api://botid-{clientId}", "api://{clientId}"], but Bot Framework tokens always have aud: "https://api.botframework.com".

Error Flow

1. botFrameworkValidator -> SigningKeyNotFoundError
   (key not at login.botframework.com, signed with Azure AD keys)
2. entraValidator -> finds key at login.microsoftonline.com/common
   -> signature valid
   -> issuer check FAILS (sts.windows.net not accepted)
   -> OR audience check FAILS (api.botframework.com not in list)

Workaround

Patch two files in the container:

jwt-validator-*.js - extend issuer check:

// Before:
if (!allowedTenantIds.some((tenantId) => iss.startsWith(`https://login.microsoftonline.com/${tenantId}/`)))

// After:
if (!allowedTenantIds.some((tenantId) => iss.startsWith(`https://login.microsoftonline.com/${tenantId}/`) || iss.startsWith(`https://sts.windows.net/${tenantId}/`)))

graph-users-*.js - add BF audience to entraValidator:

const entraValidator = new JwtValidator({
    clientId: creds.appId,
    tenantId: creds.tenantId,
    audience: ["https://api.botframework.com"],  // ADD THIS
    validateIssuer: { allowedTenantIds: [creds.tenantId] },
    jwksUriOptions: { type: "uri", uri: "https://login.microsoftonline.com/common/discovery/v2.0/keys" }
});

Environment

  • OpenClaw version: 2026.4.9 (Docker image ghcr.io/openclaw/openclaw:latest)
  • Azure Bot: SingleTenant (msaAppType: "SingleTenant")
  • Channel: MS Teams
  • Azure AD Tenant: Single-org (AzureADMyOrg)

Expected Behavior

SingleTenant bot tokens should be accepted by the msteams channel JWT validator, as the docs state SingleTenant is the recommended approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions