From a6d7ebf22e134f5b379a5d56800a4185a542c332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3n=20Levy?= Date: Sat, 16 Aug 2025 17:14:01 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20fix:=20Workaround=20for?= =?UTF-8?q?=20Federated=20OpenID=20Nonce=20Validation=20Issues=20(#9067)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: generate nonce for federated providers * fix: lint issues * chore: comments --------- Co-authored-by: Danny Avila --- api/strategies/openidStrategy.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/api/strategies/openidStrategy.js b/api/strategies/openidStrategy.js index 575521d3be..ec22b51743 100644 --- a/api/strategies/openidStrategy.js +++ b/api/strategies/openidStrategy.js @@ -99,6 +99,7 @@ class CustomOpenIDStrategy extends OpenIDStrategy { const hostAndProtocol = process.env.DOMAIN_SERVER; return new URL(`${hostAndProtocol}${req.originalUrl ?? req.url}`); } + authorizationRequestParams(req, options) { const params = super.authorizationRequestParams(req, options); if (options?.state && !params.has('state')) { @@ -112,6 +113,15 @@ class CustomOpenIDStrategy extends OpenIDStrategy { ); } + /** Generate nonce for federated providers that require it */ + const shouldGenerateNonce = isEnabled(process.env.OPENID_GENERATE_NONCE); + if (shouldGenerateNonce && !params.has('nonce') && this._sessionKey) { + const crypto = require('crypto'); + const nonce = crypto.randomBytes(16).toString('hex'); + params.set('nonce', nonce); + logger.debug('[openidStrategy] Generated nonce for federated provider:', nonce); + } + return params; } } @@ -276,12 +286,20 @@ function convertToUsername(input, defaultValue = '') { */ async function setupOpenId() { try { + const shouldGenerateNonce = isEnabled(process.env.OPENID_GENERATE_NONCE); + /** @type {ClientMetadata} */ const clientMetadata = { client_id: process.env.OPENID_CLIENT_ID, client_secret: process.env.OPENID_CLIENT_SECRET, }; + if (shouldGenerateNonce) { + clientMetadata.response_types = ['code']; + clientMetadata.grant_types = ['authorization_code']; + clientMetadata.token_endpoint_auth_method = 'client_secret_post'; + } + /** @type {Configuration} */ openidConfig = await client.discovery( new URL(process.env.OPENID_ISSUER), @@ -297,11 +315,19 @@ async function setupOpenId() { const requiredRoleParameterPath = process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH; const requiredRoleTokenKind = process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND; const usePKCE = isEnabled(process.env.OPENID_USE_PKCE); + logger.info(`[openidStrategy] OpenID authentication configuration`, { + generateNonce: shouldGenerateNonce, + reason: shouldGenerateNonce + ? 'OPENID_GENERATE_NONCE=true - Will generate nonce and use explicit metadata for federated providers' + : 'OPENID_GENERATE_NONCE=false - Standard flow without explicit nonce or metadata', + }); + const openidLogin = new CustomOpenIDStrategy( { config: openidConfig, scope: process.env.OPENID_SCOPE, callbackURL: process.env.DOMAIN_SERVER + process.env.OPENID_CALLBACK_URL, + clockTolerance: process.env.OPENID_CLOCK_TOLERANCE || 300, usePKCE, }, async (tokenset, done) => {