Skip to content

feat(msteams): add DefaultAzureCredential auth type for passwordless Teams auth#48014

Open
krrrke wants to merge 11 commits intoopenclaw:mainfrom
krrrke:feat/msteams-default-azure-credential
Open

feat(msteams): add DefaultAzureCredential auth type for passwordless Teams auth#48014
krrrke wants to merge 11 commits intoopenclaw:mainfrom
krrrke:feat/msteams-default-azure-credential

Conversation

@krrrke
Copy link
Copy Markdown

@krrrke krrrke commented Mar 16, 2026

Context

Issue #40855 requested passwordless authentication for the MS Teams channel. PR #40884 added certificate and federated credential support. PR #47934 addressed review feedback on that work — fixing schema validation, type design, env var hints, and SDK env fallback handling.

This PR builds on both to add defaultAzureCredential as a fourth auth type, completing the passwordless auth story for enterprise deployments.

Problem

Certificate and federated credential auth modes require operators to explicitly configure credential material (cert paths, FIC client IDs, workload identity assertion files). In Kubernetes deployments with Azure Workload Identity, and in local development with az login, these details are already available in the environment — but the operator still has to know which mode to select and which fields to set.

DefaultAzureCredential from @azure/identity solves this by automatically discovering credentials from the environment: workload identity, managed identity, Azure CLI, environment variables, and other sources. The operator only needs to provide appId and tenantId.

Changes

New auth type: defaultAzureCredential

{
  "channels": {
    "msteams": {
      "enabled": true,
      "appId": "<app-registration-id>",
      "tenantId": "<tenant-id>",
      "authType": "defaultAzureCredential"
    }
  }
}

DefaultAzureCredentialTokenProvider (extensions/msteams/src/sdk.ts)

  • Wraps @azure/identity's DefaultAzureCredential with the same getAccessToken(scope) interface as MsalTokenProvider
  • Passes appId as managedIdentityClientId and workloadIdentityClientId so the credential targets the correct bot identity
  • Normalizes resource URIs to AAD scopes (https://graph.microsoft.comhttps://graph.microsoft.com/.default) since DefaultAzureCredential.getToken() requires scope format, not bare resource URIs

createTokenProvider() factory (extensions/msteams/src/sdk.ts)

  • Returns DAC or MSAL provider based on authType
  • Used by all 4 token acquisition sites: probe.ts, monitor.ts, send-context.ts, graph.ts

Setup surface fix (extensions/msteams/src/setup-surface.ts)

  • When re-entering credentials as client secret via the setup wizard, authType and related fields (certPemFile, certKeyFile, ficClientId, widAssertionFile) are now reset. Previously, re-running setup on a non-client-secret config would preserve the old authType, causing resolveMSTeamsCredentials() to ignore the newly entered secret.

Release checks (extensions/msteams/package.json)

  • Added @azure/identity to rootDependencyMirrorAllowlist so scripts/release-check.ts does not report bundled extension dependency drift.

Config schema + types

  • authType union extended with "defaultAzureCredential" in both types.msteams.ts and zod-schema.providers-core.ts

Use cases

AKS with Workload Identity — the pod's service account has a federated credential. DefaultAzureCredential picks it up automatically. No secrets, no cert files, no Vault.

Local developmentaz login and go. Same config works everywhere.

Azure VMs / App Service — managed identity. Same config.

Dependencies

  • @azure/identity ^4.9.1 added as a dependency of the msteams extension (not root package.json, per plugin guidelines)

AI Disclosure

  • AI-assisted (Claude Code + Codex review — multiple review cycles)
  • Fully tested — 246/246 msteams tests passing
  • pnpm build && pnpm check && pnpm test green (pre-existing normalizeAgentId tsgo error on main excluded)
  • Authors understand what the code does
  • Codex review addressed: scope normalization, identity pinning, setup surface reset, release checks allowlist

Fixes #40855
Related: #40884, #47934

fengyang0317 and others added 9 commits March 16, 2026 01:25
…enclaw#40855)

Add support for alternative authentication methods in the MS Teams
channel, enabling passwordless bot authentication for enterprise
tenants that block client secrets via tenant-wide policy.

New config fields under channels.msteams:
- authType: 'clientSecret' (default) | 'certificate' | 'federatedCredential'
- certPemFile / certKeyFile / sendX5C: for certificate-based auth
- ficClientId: FIC client ID for federated credential auth
- widAssertionFile: workload identity assertion file path

Fully backward compatible: existing appId + appPassword configs
continue to work unchanged.
Test coverage for resolveMSTeamsCredentials and hasConfiguredMSTeamsCredentials
across all three auth types introduced in the preceding commit. Fixed existing
test expectation to include authType field in resolved credentials result.
Verifies certificate auth requires both cert files, federated auth requires
ficClientId or widAssertionFile, and clientSecret is the default authType.
Variable appPassword in MSTeamsCredentials (token.ts) was declared as
required but set to empty string for certificate and federated credential
auth paths. Changed to optional to accurately represent the type contract.
Guarded clientSecret assignment in sdk.ts to handle undefined appPassword.
Added test assertions verifying appPassword is undefined for certificate
and federated credential auth.
Type MSTeamsAuthType in token.ts was a standalone union duplicating the
inline type in MSTeamsConfig.authType (types.msteams.ts). Replaced with
NonNullable<MSTeamsConfig["authType"]> to maintain a single source of
truth. Adding a new auth method now only requires updating types.msteams.ts.
Fields authType, certPemFile, certKeyFile, sendX5C, ficClientId, and
widAssertionFile were added to MSTeamsConfig (types.msteams.ts) but not
to MSTeamsConfigSchema (zod-schema.providers-core.ts). The strict Zod
schema rejected configs using these fields as unrecognized properties.
Added cross-field validation in superRefine: authType "certificate"
requires certPemFile and certKeyFile, authType "federatedCredential"
requires ficClientId or widAssertionFile.
Error messages in errors.ts and probe.ts dropped references to environment
variables MSTEAMS_APP_ID and MSTEAMS_TENANT_ID when the new auth types were
added. Restored env var hints alongside the new auth type guidance so users
configuring via environment variables still get actionable error messages.
Added test assertions verifying env var names appear in auth error hint.
Added presence helpers that check certificate and federated credential
auth material across typed config, SDK plain env vars (certPemFile,
certKeyFile, FICClientId, WIDAssertionFile), and SDK connection env
vars (connections__*__settings__*). Credential resolution and
hasConfiguredMSTeamsCredentials now reject empty configs that have no
auth material in any source, preventing silent fallthrough to managed
identity auth in the SDK. Configs backed by SDK env vars are accepted.
Aligned certificate case in buildMSTeamsAuthConfig with federated case —
only assign certPemFile/certKeyFile to base when present, so undefined
values do not shadow the SDK's env var resolution. Added explicit test
assertions documenting that env-only cert paths are not forwarded in
credentials (SDK merges them at runtime).
New authType "defaultAzureCredential" defers credential selection to
@azure/identity's DefaultAzureCredential, which tries environment
variables, workload identity, managed identity, az cli, and other
sources automatically. Enables passwordless Teams auth in AKS via
workload identity without explicit certificate or secret management.

Added DefaultAzureCredentialTokenProvider in sdk.ts as a drop-in
replacement for MsalTokenProvider. Added createTokenProvider() factory
that returns the appropriate provider based on authType. Updated all
4 call sites (probe, monitor, send-context, graph) to use the factory.

Added @azure/identity as a dependency of the msteams extension.
@openclaw-barnacle openclaw-barnacle bot added channel: msteams Channel integration: msteams size: L labels Mar 16, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 16, 2026

Greptile Summary

This PR adds defaultAzureCredential as a fourth auth type for the MS Teams channel, completing the passwordless auth story for enterprise deployments. It introduces DefaultAzureCredentialTokenProvider wrapping @azure/identity's DefaultAzureCredential, a createTokenProvider factory used consistently across all four token-acquisition sites, correct scope normalisation (/.default suffix), and a setup-wizard fix that resets stale auth fields when the user re-enters client-secret credentials. The changes are well-tested with 109 new test cases covering all auth types.

  • New defaultAzureCredential auth type — only appId and tenantId are required; DAC discovers workload identity, managed identity, Azure CLI, etc. automatically.
  • DefaultAzureCredentialTokenProvider — lazily initialises @azure/identity's DefaultAzureCredential, pins identity via managedIdentityClientId/workloadIdentityClientId, and normalises bare resource URIs to AAD scopes.
  • createTokenProvider factory — consistently replaces direct new sdk.MsalTokenProvider(authConfig) calls in probe.ts, monitor.ts, send-context.ts, and graph.ts.
  • Setup-wizard fix — re-entering client-secret credentials now resets authType and all cert/FIC fields, preventing leftover auth types from shadowing the new secret.
  • Minor style issue — in token.ts, the JSDoc block for hasCertificateAuthMaterial is placed above trimPresence instead of above the function it documents; IDE tooling and doc generators will misattribute it.

Confidence Score: 4/5

  • Safe to merge; one misplaced docblock comment is the only issue found
  • The implementation is architecturally sound: DefaultAzureCredentialTokenProvider correctly implements the getAccessToken interface, scope normalisation is handled, identity is pinned via both managedIdentityClientId and workloadIdentityClientId, and the setup-wizard regression is fixed. All four token-acquisition paths are updated consistently. Test coverage is thorough (109 new cases). The only issue is a misplaced JSDoc block in token.ts that doesn't affect runtime behaviour.
  • No files require special attention beyond the minor docblock order fix in extensions/msteams/src/token.ts.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/token.ts
Line: 23-33

Comment:
**Misplaced JSDoc block**

The multi-line JSDoc comment for `hasCertificateAuthMaterial` (lines 23–32) is placed immediately above `trimPresence`, not above the function it describes. As written, documentation generators and IDE tooling will attach this docblock to `trimPresence` and leave `hasCertificateAuthMaterial` undocumented.

Move the block directly above `hasCertificateAuthMaterial` and the single-line comment directly above `trimPresence`:

```suggestion
/** Return trimmed value if non-blank, otherwise undefined. */
function trimPresence(value: string | undefined | null): string | undefined {
```

Then place the multi-line block just before `function hasCertificateAuthMaterial(`.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: fc79604

Comment on lines +23 to +33
/**
* Check whether certificate auth material is present in typed config
* or the SDK's plain env vars. Each field is checked independently so
* mixed-source configs (e.g. certPemFile in config, certKeyFile in env)
* are accepted — the SDK merges them field-by-field at runtime.
*
* Does not check connections__*__settings__* env vars because those may
* belong to an unrelated Agents SDK connection and cannot be reliably
* attributed to the Teams channel.
*/
/** Return trimmed value if non-blank, otherwise undefined. */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misplaced JSDoc block

The multi-line JSDoc comment for hasCertificateAuthMaterial (lines 23–32) is placed immediately above trimPresence, not above the function it describes. As written, documentation generators and IDE tooling will attach this docblock to trimPresence and leave hasCertificateAuthMaterial undocumented.

Move the block directly above hasCertificateAuthMaterial and the single-line comment directly above trimPresence:

Suggested change
/**
* Check whether certificate auth material is present in typed config
* or the SDK's plain env vars. Each field is checked independently so
* mixed-source configs (e.g. certPemFile in config, certKeyFile in env)
* are accepted the SDK merges them field-by-field at runtime.
*
* Does not check connections__*__settings__* env vars because those may
* belong to an unrelated Agents SDK connection and cannot be reliably
* attributed to the Teams channel.
*/
/** Return trimmed value if non-blank, otherwise undefined. */
/** Return trimmed value if non-blank, otherwise undefined. */
function trimPresence(value: string | undefined | null): string | undefined {

Then place the multi-line block just before function hasCertificateAuthMaterial(.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/token.ts
Line: 23-33

Comment:
**Misplaced JSDoc block**

The multi-line JSDoc comment for `hasCertificateAuthMaterial` (lines 23–32) is placed immediately above `trimPresence`, not above the function it describes. As written, documentation generators and IDE tooling will attach this docblock to `trimPresence` and leave `hasCertificateAuthMaterial` undocumented.

Move the block directly above `hasCertificateAuthMaterial` and the single-line comment directly above `trimPresence`:

```suggestion
/** Return trimmed value if non-blank, otherwise undefined. */
function trimPresence(value: string | undefined | null): string | undefined {
```

Then place the multi-line block just before `function hasCertificateAuthMaterial(`.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc7960465d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

"description": "OpenClaw Microsoft Teams channel plugin",
"type": "module",
"dependencies": {
"@azure/identity": "^4.9.1",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Regenerate lockfile after adding msteams dependency

Adding @azure/identity here without a matching pnpm-lock.yaml update makes workspace installs fail under frozen lockfile mode; running pnpm install --frozen-lockfile --lockfile-only --ignore-scripts already errors with ERR_PNPM_OUTDATED_LOCKFILE for extensions/msteams/package.json, and CI uses pnpm install --frozen-lockfile in .github/workflows/ci.yml (line 515), so this change will block reproducible installs and CI for the commit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05a9a0f57b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +36 to +37
managedIdentityClientId: this.clientId,
workloadIdentityClientId: this.clientId,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop pinning DAC identity client IDs to appId

This hard-codes both managedIdentityClientId and workloadIdentityClientId to the Teams bot appId, which breaks defaultAzureCredential when the bot app registration ID and the user-assigned managed identity ID differ (a supported scenario in this plugin via separate FIC identity fields). In AKS/workload-identity deployments, overriding these options forces token acquisition to the wrong principal and can make auth fail even when the environment is correctly configured. Leave these options unset by default or allow a separate identity client ID input.

Useful? React with 👍 / 👎.

Comment on lines +69 to +71
const appId = normalizeSecretInputString(cfg?.appId);
const tenantId = normalizeSecretInputString(cfg?.tenantId);
if (!appId || !tenantId) return false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize env-backed appId/tenantId as configured

hasConfiguredMSTeamsCredentials now only reads cfg.appId/cfg.tenantId, while resolveMSTeamsCredentials still accepts MSTEAMS_APP_ID and MSTEAMS_TENANT_ID. In the setup finalize flow, this mismatch makes passwordless auth (defaultAzureCredential) appear unconfigured when IDs are env-backed, so the wizard can drop into the client-secret prompt path and overwrite the existing auth mode. The configured check should mirror resolver fallback for appId/tenantId.

Useful? React with 👍 / 👎.

CloudAdapter.process() acquires outbound tokens via MsalTokenProvider,
which has its own credential chain (WID → FIC → secret → cert → MI).
With defaultAzureCredential, none of these fields were set, causing
fallthrough to ManagedIdentityApplication — which does not support
AKS workload identity federation.

Detect AZURE_FEDERATED_TOKEN_FILE (injected by the workload identity
webhook) and pass it as WIDAssertionFile so the SDK uses its native
WID path. Falls back to @azure/identity DefaultAzureCredential for
environments without workload identity.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b85ec0e0e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +108 to +109
if (authConfig.WIDAssertionFile) {
return new sdk.MsalTokenProvider(authConfig);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep defaultAzureCredential on DAC path

When authType is defaultAzureCredential, this branch switches to MsalTokenProvider whenever authConfig.WIDAssertionFile is set, which is common in AKS because AZURE_FEDERATED_TOKEN_FILE is injected automatically. That bypasses DefaultAzureCredential's multi-source fallback chain (CLI, managed identity, env credentials), so any partial or mismatched workload-identity setup turns into a hard auth failure instead of falling back to other available credentials in the same environment.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional and required. The issue isn't about the explicit token provider we create — it's about CloudAdapter.process().

CloudAdapter.process() internally creates its own MsalTokenProvider from the authConfig to acquire outbound tokens (connector client + user token client). We don't control that code path. The SDK's MsalTokenProvider has a credential chain: WIDAssertionFileFICClientIdclientSecret → certificate → ManagedIdentityApplication. Without WIDAssertionFile in the authConfig, it falls through to ManagedIdentityApplication, which does not support AKS workload identity federation — that was the root cause of the inbound webhook failure.

Setting WIDAssertionFile puts the SDK's internal token acquisition onto the correct WID code path. Our DefaultAzureCredentialTokenProvider is only used for the explicit token provider — it has no effect on what the adapter does internally.

Additionally, DAC's multi-source fallback is a liability in this context: silently falling back to a different credential (az cli, managed identity, env vars) when workload identity is misconfigured would authenticate as the wrong identity. Hard failure is the correct behavior.

@Emrullah007
Copy link
Copy Markdown

Hi, reviewing this from enterprise deployment perspective. This is exactly the auth pattern we need. In our environment client secrets and uploaded certificates are not allowed so managed identity is the only path.
A couple things I want to understand better:

The DefaultAzureCredential with managedIdentityClientId pinned to appId. Does this work when the bot app registration and the managed identity are different? The Codex comment on this point seems worth addressing.

For Azure Container Apps (not AKS) the workload identity setup is a bit different. Would be good to call this out in the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support federated credentials / managed identity for MS Teams Bot Framework authentication

3 participants