From e6068bfdc4bdbb2603de7a84cf7417c14337686f Mon Sep 17 00:00:00 2001 From: Rushi Gandhi Date: Wed, 16 Oct 2024 18:27:29 +0530 Subject: [PATCH 1/4] Add middleware that verifies refresh token --- Server/middleware/verifyJWT.js | 58 +++++++++++++++++++++++++++++++++- Server/utils/messages.js | 3 ++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 8f6bbecfe..53d0ea90a 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -2,6 +2,9 @@ const jwt = require("jsonwebtoken"); const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; const { errorMessages } = require("../utils/messages"); +const { getTokenFromHeaders } = require("../utils/utils"); +const { handleError } = require("../controllers/controllerUtils"); + /** * Verifies the JWT token * @function @@ -48,4 +51,57 @@ const verifyJWT = (req, res, next) => { }); }; -module.exports = { verifyJWT }; +/** + * Verifies the Refresh token + * @function + * @param {express.Request} req + * @param {express.Response} res + * @param {express.NextFunction} next + * @property {Object} req.body - The Refresh Token & JWT Token will be passed in body of the request. + * @returns {express.Response} + */ +const verifyRefreshToken = (req, res, next) => { + try { + const jwtToken = getTokenFromHeaders(req.headers); + // Make sure a jwtToken is provided + if (!jwtToken) { + const error = new Error(errorMessages.NO_AUTH_TOKEN); + error.status = 401; + error.service = SERVICE_NAME; + error.method = "verifyRefreshToken"; + next(error); + return; + } + + const { refreshToken } = req.body; + // Make sure refreshTokens is provided + if (!refreshToken) { + const error = new Error(errorMessages.NO_REFRESH_TOKEN); + error.status = 401; + error.service = SERVICE_NAME; + error.method = "verifyRefreshToken"; + next(error); + return; + } + + // Verify the refreshToken's authenticity + const { refreshTokenSecret } = req.settingsService.getSettings(); + jwt.verify(refreshToken, refreshTokenSecret, (err, decoded) => { + if (err) { + const errorMessage = + err.name === "TokenExpiredError" + ? errorMessages.EXPIRED_REFRESH_TOKEN + : errorMessages.INVALID_REFRESH_TOKEN; + return res.status(401).json({ success: false, msg: errorMessage }); + } + + // Authenticity of refreshToken is verified, now we can decode jwtToken for payload + req.user = jwt.decode(jwtToken); + next(); + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "verifyRefreshToken")); + } +}; + +module.exports = { verifyJWT, verifyRefreshToken }; diff --git a/Server/utils/messages.js b/Server/utils/messages.js index d685e7326..8cb7325fd 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -13,6 +13,9 @@ const errorMessages = { NO_AUTH_TOKEN: "No auth token provided", INVALID_AUTH_TOKEN: "Invalid auth token", EXPIRED_AUTH_TOKEN: "Token expired", + NO_REFRESH_TOKEN: "No refresh token provided", + INVALID_REFRESH_TOKEN: "Invalid refresh token", + EXPIRED_REFRESH_TOKEN: "Refresh token expired", //Ownership Middleware VERIFY_OWNER_NOT_FOUND: "Document not found", From 412ac3312b8325bc7afa0bc32c98f0604dea687d Mon Sep 17 00:00:00 2001 From: Rushi Gandhi Date: Wed, 16 Oct 2024 19:21:22 +0530 Subject: [PATCH 2/4] use jwt.verify instead of jwt.decoded to verify authenticity of token --- Server/middleware/verifyJWT.js | 14 +++++++++++++- Server/utils/messages.js | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 53d0ea90a..90d8e1238 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -96,7 +96,19 @@ const verifyRefreshToken = (req, res, next) => { } // Authenticity of refreshToken is verified, now we can decode jwtToken for payload - req.user = jwt.decode(jwtToken); + const jwtSecret = req.settingsService.getSettings().jwtSecret; + const decoded = jwt.verify(jwtToken, jwtSecret, { ignoreExpiration: true }); + + if (!decoded) { + const error = new Error(errorMessages.INVALID_PAYLOAD); + error.status = 401; + error.service = SERVICE_NAME; + error.method = "verifyRefreshToken"; + next(error); + return; + } + + req.user = decoded; next(); }); } catch (error) { diff --git a/Server/utils/messages.js b/Server/utils/messages.js index 8cb7325fd..b29f806e7 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -17,6 +17,9 @@ const errorMessages = { INVALID_REFRESH_TOKEN: "Invalid refresh token", EXPIRED_REFRESH_TOKEN: "Refresh token expired", + //Payload + INVALID_PAYLOAD: "Invalid payload", + //Ownership Middleware VERIFY_OWNER_NOT_FOUND: "Document not found", VERIFY_OWNER_UNAUTHORIZED: "Unauthorized access", From 3e9593fa233f4c0c3fa3eef11245fd90b05ca6da Mon Sep 17 00:00:00 2001 From: Rushi Gandhi Date: Thu, 17 Oct 2024 11:58:13 +0530 Subject: [PATCH 3/4] Refactor verifyJWT middleware to handle refresh tokens alongside access token --- Server/middleware/verifyJWT.js | 125 +++++++++++++-------------------- Server/utils/messages.js | 1 + 2 files changed, 51 insertions(+), 75 deletions(-) diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 90d8e1238..50935772e 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -2,7 +2,6 @@ const jwt = require("jsonwebtoken"); const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; const { errorMessages } = require("../utils/messages"); -const { getTokenFromHeaders } = require("../utils/utils"); const { handleError } = require("../controllers/controllerUtils"); /** @@ -20,7 +19,7 @@ const verifyJWT = (req, res, next) => { const error = new Error(errorMessages.NO_AUTH_TOKEN); error.status = 401; error.service = SERVICE_NAME; - next(error); + next(handleError(error)); return; } // Make sure it is properly formatted @@ -29,7 +28,7 @@ const verifyJWT = (req, res, next) => { error.status = 400; error.service = SERVICE_NAME; error.method = "verifyJWT"; - next(error); + next(handleError(error)); return; } @@ -38,82 +37,58 @@ const verifyJWT = (req, res, next) => { const { jwtSecret } = req.settingsService.getSettings(); jwt.verify(parsedToken, jwtSecret, (err, decoded) => { if (err) { - const errorMessage = - err.name === "TokenExpiredError" - ? errorMessages.EXPIRED_AUTH_TOKEN - : errorMessages.INVALID_AUTH_TOKEN; - return res.status(401).json({ success: false, msg: errorMessage }); + if (err.name === "TokenExpiredError") { + // token has expired + handleExpiredJwtToken(req, res, next); + } + else { + // Invalid token (signature or token altered or other issue) + const errorMessage = errorMessages.INVALID_AUTH_TOKEN; + return res.status(401).json({ success: false, msg: errorMessage }); + } + } + else { + // Token is valid and not expired, carry on with request, Add the decoded payload to the request + req.user = decoded; + next(); } - - // Add the user to the request object for use in the route - req.user = decoded; - next(); }); }; -/** - * Verifies the Refresh token - * @function - * @param {express.Request} req - * @param {express.Response} res - * @param {express.NextFunction} next - * @property {Object} req.body - The Refresh Token & JWT Token will be passed in body of the request. - * @returns {express.Response} - */ -const verifyRefreshToken = (req, res, next) => { - try { - const jwtToken = getTokenFromHeaders(req.headers); - // Make sure a jwtToken is provided - if (!jwtToken) { - const error = new Error(errorMessages.NO_AUTH_TOKEN); - error.status = 401; - error.service = SERVICE_NAME; - error.method = "verifyRefreshToken"; - next(error); - return; - } +function handleExpiredJwtToken(req, res, next) { + // check for refreshToken + const refreshToken = req.headers["x-refresh-token"]; - const { refreshToken } = req.body; - // Make sure refreshTokens is provided - if (!refreshToken) { - const error = new Error(errorMessages.NO_REFRESH_TOKEN); - error.status = 401; - error.service = SERVICE_NAME; - error.method = "verifyRefreshToken"; - next(error); - return; - } - - // Verify the refreshToken's authenticity - const { refreshTokenSecret } = req.settingsService.getSettings(); - jwt.verify(refreshToken, refreshTokenSecret, (err, decoded) => { - if (err) { - const errorMessage = - err.name === "TokenExpiredError" - ? errorMessages.EXPIRED_REFRESH_TOKEN - : errorMessages.INVALID_REFRESH_TOKEN; - return res.status(401).json({ success: false, msg: errorMessage }); - } - - // Authenticity of refreshToken is verified, now we can decode jwtToken for payload - const jwtSecret = req.settingsService.getSettings().jwtSecret; - const decoded = jwt.verify(jwtToken, jwtSecret, { ignoreExpiration: true }); - - if (!decoded) { - const error = new Error(errorMessages.INVALID_PAYLOAD); - error.status = 401; - error.service = SERVICE_NAME; - error.method = "verifyRefreshToken"; - next(error); - return; - } - - req.user = decoded; - next(); - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "verifyRefreshToken")); + if (!refreshToken) { + // No refresh token provided + const error = new Error(errorMessages.NO_REFRESH_TOKEN); + error.status = 401; + error.service = SERVICE_NAME; + error.method = "handleExpiredJwtToken"; + return next(handleError(error)); } -}; -module.exports = { verifyJWT, verifyRefreshToken }; + // Verify refresh token + const { refreshTokenSecret } = req.settingsService.getSettings(); + jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => { + if (refreshErr) { + // Invalid or expired refresh token, trigger logout + const errorMessage = + refreshErr.name === "TokenExpiredError" + ? errorMessages.EXPIRED_REFRESH_TOKEN + : errorMessages.INVALID_REFRESH_TOKEN; + const error = new Error(errorMessage); + error.status = 401; + error.service = SERVICE_NAME; + return next(handleError(error)); + } + + // Refresh token is valid and unexpired, request for new access token + res.status(403).json({ + success: false, + msg: errorMessages.REQUEST_NEW_ACCESS_TOKEN, + }); + }); +} + +module.exports = { verifyJWT }; diff --git a/Server/utils/messages.js b/Server/utils/messages.js index b29f806e7..f06a556c0 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -16,6 +16,7 @@ const errorMessages = { NO_REFRESH_TOKEN: "No refresh token provided", INVALID_REFRESH_TOKEN: "Invalid refresh token", EXPIRED_REFRESH_TOKEN: "Refresh token expired", + REQUEST_NEW_ACCESS_TOKEN: "Request new access token", //Payload INVALID_PAYLOAD: "Invalid payload", From fe503353c5e003c0fbb276a8bc53b819151e9e09 Mon Sep 17 00:00:00 2001 From: Rushi Gandhi Date: Thu, 17 Oct 2024 12:26:34 +0530 Subject: [PATCH 4/4] remove handleError from verifyJWT --- Server/middleware/verifyJWT.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index ca414b23d..23b79e2c8 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -1,6 +1,5 @@ import jwt from "jsonwebtoken"; import { errorMessages } from "../utils/messages.js"; -import { handleError } from "../controllers/controllerUtils.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; @@ -20,7 +19,7 @@ const verifyJWT = (req, res, next) => { const error = new Error(errorMessages.NO_AUTH_TOKEN); error.status = 401; error.service = SERVICE_NAME; - next(handleError(error)); + next(error); return; } // Make sure it is properly formatted @@ -29,7 +28,7 @@ const verifyJWT = (req, res, next) => { error.status = 400; error.service = SERVICE_NAME; error.method = "verifyJWT"; - next(handleError(error)); + next(error); return; } @@ -66,7 +65,7 @@ function handleExpiredJwtToken(req, res, next) { error.status = 401; error.service = SERVICE_NAME; error.method = "handleExpiredJwtToken"; - return next(handleError(error)); + return next(error); } // Verify refresh token @@ -81,7 +80,7 @@ function handleExpiredJwtToken(req, res, next) { const error = new Error(errorMessage); error.status = 401; error.service = SERVICE_NAME; - return next(handleError(error)); + return next(error); } // Refresh token is valid and unexpired, request for new access token @@ -92,5 +91,4 @@ function handleExpiredJwtToken(req, res, next) { }); } -export { verifyJWT }; - +export { verifyJWT }; \ No newline at end of file