From 7e6b4d70b94a8039fb6974404798f23c172c4533 Mon Sep 17 00:00:00 2001 From: sudie-codes Date: Sat, 11 Apr 2026 19:19:41 -0700 Subject: [PATCH] fix(msteams): accept SingleTenant sts.windows.net issuer in JWT validator (#64270) (#64641) --- extensions/msteams/src/sdk.test.ts | 24 +++++++++++++++++++----- extensions/msteams/src/sdk.ts | 6 +++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/extensions/msteams/src/sdk.test.ts b/extensions/msteams/src/sdk.test.ts index 045c20e667d..6755e3b004b 100644 --- a/extensions/msteams/src/sdk.test.ts +++ b/extensions/msteams/src/sdk.test.ts @@ -289,9 +289,12 @@ describe("createBotFrameworkJwtValidator", () => { expect(opts.issuer as string[]).toContain("https://login.microsoftonline.com/tenant-id/v2.0"); }); - it("validates a token with STS Windows issuer", async () => { + it("validates a SingleTenant token with tenant-scoped STS Windows issuer (#64270)", async () => { + // Regression for #64270: the sts.windows.net issuer was hardcoded to a + // single tenant UUID, so every other SingleTenant bot deployment hit 401. + // The tenant-aware form must accept the deployment's own tenant. jwtState.decodedPayload = { - iss: "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + iss: `https://sts.windows.net/${creds.tenantId}/`, }; const validator = await createBotFrameworkJwtValidator(creds); @@ -299,9 +302,20 @@ describe("createBotFrameworkJwtValidator", () => { expect(jwtState.verifyCalls).toHaveLength(1); const opts = jwtState.verifyCalls[0]?.options as Record; - expect(opts.issuer as string[]).toContain( - "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", - ); + expect(opts.issuer as string[]).toContain(`https://sts.windows.net/${creds.tenantId}/`); + }); + + it("rejects STS Windows tokens issued by a different tenant (#64270)", async () => { + // Guardrail against regressing back to a hardcoded tenant: the previously + // hardcoded UUID must NOT be accepted when the bot is configured for a + // different tenant. This also prevents cross-tenant token reuse. + jwtState.decodedPayload = { + iss: "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + }; + + const validator = await createBotFrameworkJwtValidator(creds); + await expect(validator.validate("Bearer token-sts-other-tenant")).resolves.toBe(false); + expect(jwtState.verifyCalls).toHaveLength(0); }); it("rejects tokens with unknown issuer", async () => { diff --git a/extensions/msteams/src/sdk.ts b/extensions/msteams/src/sdk.ts index 6612498c38a..856f0cac1ee 100644 --- a/extensions/msteams/src/sdk.ts +++ b/extensions/msteams/src/sdk.ts @@ -644,7 +644,11 @@ const BOT_FRAMEWORK_ISSUERS: ReadonlyArray<{ jwksUri: "https://login.microsoftonline.com/common/discovery/v2.0/keys", }, { - issuer: "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + // SingleTenant bot deployments (Microsoft's default since 2025-07-31) get + // tokens signed by the Azure AD v1 endpoint, whose issuer is scoped to the + // bot's tenant. This must be a function so each deployment accepts its own + // tenant rather than a single hardcoded one (#64270). + issuer: (tenantId: string) => `https://sts.windows.net/${tenantId}/`, jwksUri: "https://login.microsoftonline.com/common/discovery/v2.0/keys", }, ];