From d02867a3e2c0012705dc151008d43e1b62dd6100 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 20 Jul 2026 21:05:20 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=8D=20refactor:=20Surface=20primary=20?= =?UTF-8?q?OpenID=20JWT=20failure=20reason=20in=20auth=20failure=20log=20(?= =?UTF-8?q?#14346)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When OPENID_REUSE_TOKENS is enabled and both the openidJwt strategy and the HS256 jwt fallback fail, the final 'Authentication failed after all strategies' warn log reported only the fallback's reason. For an RS256 provider (Keycloak, Auth0, Okta) that surfaces as 'invalid algorithm', which is the HS256 fallback rejecting the provider token, not the real reason openidJwt did not authenticate, and it was previously only visible at debug level. Include the captured primary (openidJwt) failure reason and error name in the final warn log so reused-token failures are diagnosable without enabling debug and are not misattributed to the fallback. Refs #14311 --- api/server/middleware/__tests__/requireJwtAuth.spec.js | 5 +++++ api/server/middleware/requireJwtAuth.js | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/api/server/middleware/__tests__/requireJwtAuth.spec.js b/api/server/middleware/__tests__/requireJwtAuth.spec.js index b70f371a94..64e00e4aa4 100644 --- a/api/server/middleware/__tests__/requireJwtAuth.spec.js +++ b/api/server/middleware/__tests__/requireJwtAuth.spec.js @@ -564,12 +564,17 @@ describe('requireJwtAuth tenant context chaining', () => { fallback_strategy: 'jwt', fallback_attempted: true, fallback_succeeded: false, + // The real openidJwt failure is surfaced alongside the fallback's reason so a + // reused-token failure is not misattributed to the `jwt` fallback's error (#14311). + primary_failure_reason: 'jwt expired', + primary_failure_error_name: 'TokenExpiredError', reason: 'invalid signature', error_name: 'JsonWebTokenError', status: 401, }), ); expect(logger.warn.mock.calls[0][0]).toContain('"reason":"invalid signature"'); + expect(logger.warn.mock.calls[0][0]).toContain('"primary_failure_reason":"jwt expired"'); expect(logger.warn.mock.calls[0][0]).toContain('"path":"/api/ask"'); }); diff --git a/api/server/middleware/requireJwtAuth.js b/api/server/middleware/requireJwtAuth.js index 9c4d1ca47c..b4877bcdc5 100644 --- a/api/server/middleware/requireJwtAuth.js +++ b/api/server/middleware/requireJwtAuth.js @@ -121,6 +121,14 @@ const requireJwtAuth = (req, res, next) => { fallback_succeeded: false, attempted_strategies: strategies, final_strategy: strategy, + // Surface the primary (openidJwt) failure alongside the final strategy's reason so a + // reused-token failure is not misattributed to the HS256 `jwt` fallback's "invalid + // algorithm" error, which is the fallback rejecting an RS256 provider token, not the + // real reason openidJwt did not authenticate. + ...(fallbackAttempted && { + primary_failure_reason: primaryFailureReason, + primary_failure_error_name: primaryFailureErrorName, + }), reason: getAuthFailureReason(err, info), error_name: getAuthFailureErrorName(err, info), status: status || 401,