From cc51ce4e7319744eab7896d5ce8e17a3d23503fd Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 9 Feb 2026 15:43:34 -0500 Subject: [PATCH] fix: refresh token handling when reusing OpenID Tokens with no offline_access claim - Updated the refreshController to check for access token and user ID when a refresh token is not provided, improving user session management. - Added logic to handle expired access tokens, redirecting users to the login page if necessary. - Enhanced logging in setOpenIDAuthTokens to provide clearer warnings when no refresh token is available, improving traceability of session issues. --- api/server/controllers/AuthController.js | 37 +++++++++++++++++++++++- api/server/services/AuthService.js | 6 ++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/api/server/controllers/AuthController.js b/api/server/controllers/AuthController.js index 22e53dcfc9..5348ed41df 100644 --- a/api/server/controllers/AuthController.js +++ b/api/server/controllers/AuthController.js @@ -74,7 +74,42 @@ const refreshController = async (req, res) => { const refreshToken = req.session?.openidTokens?.refreshToken || parsedCookies.refreshToken; if (!refreshToken) { - return res.status(200).send('Refresh token not provided'); + const accessToken = req.session?.openidTokens?.accessToken; + const storedUserId = req.session?.openidTokens?.userId; + + if (!accessToken || !storedUserId) { + return res.status(200).send('Refresh token not provided'); + } + + let tokenExpired = false; + try { + const decoded = jwt.decode(accessToken); + if (decoded?.exp && decoded.exp < Date.now() / 1000) { + tokenExpired = true; + } + } catch { + const expiresAt = req.session?.openidTokens?.expiresAt; + if (expiresAt && expiresAt < Date.now()) { + tokenExpired = true; + } + } + + if (tokenExpired) { + logger.warn('[refreshController] Access token expired and no refresh token available'); + return res.status(401).redirect('/login'); + } + + const user = await getUserById(storedUserId, '-password -__v -totpSecret -backupCodes'); + if (!user) { + return res.status(401).redirect('/login'); + } + + user.federatedTokens = { + access_token: accessToken, + expires_at: req.session?.openidTokens?.expiresAt, + }; + + return res.status(200).send({ token: accessToken, user }); } try { diff --git a/api/server/services/AuthService.js b/api/server/services/AuthService.js index a400bce8b7..101e748217 100644 --- a/api/server/services/AuthService.js +++ b/api/server/services/AuthService.js @@ -444,8 +444,9 @@ const setOpenIDAuthTokens = (tokenset, req, res, userId, existingRefreshToken) = const refreshToken = tokenset.refresh_token || existingRefreshToken; if (!refreshToken) { - logger.error('[setOpenIDAuthTokens] No refresh token available'); - return; + logger.warn( + '[setOpenIDAuthTokens] No refresh token available — session will not auto-refresh', + ); } /** Store tokens server-side in session to avoid large cookies */ @@ -454,6 +455,7 @@ const setOpenIDAuthTokens = (tokenset, req, res, userId, existingRefreshToken) = accessToken: tokenset.access_token, refreshToken: refreshToken, expiresAt: expirationDate.getTime(), + userId: userId, }; } else { logger.warn('[setOpenIDAuthTokens] No session available, falling back to cookies');