fix: preserve Teams Entra JWT fallback on legacy validator errors

This commit is contained in:
Peter Steinberger
2026-03-29 09:15:08 +09:00
parent 5872f860c9
commit 3d69ad8308
2 changed files with 51 additions and 12 deletions

View File

@@ -173,6 +173,38 @@ describe("createBotFrameworkJwtValidator", () => {
expect(entraConfig?.validateIssuer).toEqual({ allowedTenantIds: ["tenant-id"] });
});
it("falls back to Entra JWKS when Bot Framework validation throws", async () => {
jwtValidatorState.behaviorByJwks.set(
"https://login.botframework.com/v1/.well-known/keys",
"throw",
);
jwtValidatorState.behaviorByJwks.set(
"https://login.microsoftonline.com/common/discovery/v2.0/keys",
"success",
);
const validator = await createBotFrameworkJwtValidator(creds);
await expect(
validator.validate("Bearer token-throw", "https://service.example.com"),
).resolves.toBe(true);
expect(jwtValidatorState.calls).toHaveLength(2);
expect(jwtValidatorState.calls[0]).toMatchObject({
jwksUri: "https://login.botframework.com/v1/.well-known/keys",
token: "token-throw",
overrideOptions: {
validateServiceUrl: { expectedServiceUrl: "https://service.example.com" },
},
});
expect(jwtValidatorState.calls[1]).toMatchObject({
jwksUri: "https://login.microsoftonline.com/common/discovery/v2.0/keys",
token: "token-throw",
overrideOptions: {
validateServiceUrl: { expectedServiceUrl: "https://service.example.com" },
},
});
});
it("returns false when all validator paths fail", async () => {
jwtValidatorState.behaviorByJwks.set(
"https://login.botframework.com/v1/.well-known/keys",
@@ -181,6 +213,7 @@ describe("createBotFrameworkJwtValidator", () => {
const validator = await createBotFrameworkJwtValidator(creds);
await expect(validator.validate("Bearer token-3")).resolves.toBe(false);
expect(jwtValidatorState.calls).toHaveLength(2);
});
it("returns false for empty bearer token", async () => {

View File

@@ -439,6 +439,23 @@ export async function createBotFrameworkJwtValidator(creds: MSTeamsCredentials):
},
});
async function validateWithFallback(
token: string,
overrides: { validateServiceUrl: { expectedServiceUrl: string } } | undefined,
): Promise<boolean> {
for (const validator of [botFrameworkValidator, entraValidator]) {
try {
const result = await validator.validateAccessToken(token, overrides);
if (result != null) {
return true;
}
} catch {
continue;
}
}
return false;
}
return {
async validate(authHeader: string, serviceUrl?: string): Promise<boolean> {
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
@@ -449,18 +466,7 @@ export async function createBotFrameworkJwtValidator(creds: MSTeamsCredentials):
const overrides = serviceUrl
? ({ validateServiceUrl: { expectedServiceUrl: serviceUrl } } as const)
: undefined;
try {
const legacyResult = await botFrameworkValidator.validateAccessToken(token, overrides);
if (legacyResult != null) {
return true;
}
const entraResult = await entraValidator.validateAccessToken(token, overrides);
return entraResult != null;
} catch {
return false;
}
return await validateWithFallback(token, overrides);
},
};
}