From b060ad1b8e0bbc61c20dc25be553bba4cc4d2716 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sat, 30 Apr 2022 05:04:27 +0500 Subject: [PATCH 1/2] fix: added CSRF check for granting access via session authentication --- api/package-lock.json | 14 ++++++++++++++ api/package.json | 1 + api/src/app.ts | 6 ++++++ api/src/middlewares/authenticateToken.ts | 7 ++++++- api/src/routes/web/index.ts | 9 ++++++++- api/src/routes/web/web.ts | 5 ----- web/src/components/login.tsx | 8 +------- web/src/context/appContext.tsx | 1 + 8 files changed, 37 insertions(+), 14 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index a0c9dd0..a3b8311 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -17,6 +17,7 @@ "csurf": "^1.11.0", "express": "^4.17.1", "express-session": "^1.17.2", + "helmet": "^5.0.2", "joi": "^17.4.2", "jsonwebtoken": "^8.5.1", "mongoose": "^6.0.12", @@ -4817,6 +4818,14 @@ "node": ">=8" } }, + "node_modules/helmet": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.0.2.tgz", + "integrity": "sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -14126,6 +14135,11 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, + "helmet": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.0.2.tgz", + "integrity": "sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==" + }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", diff --git a/api/package.json b/api/package.json index 4587d7c..c0c201e 100644 --- a/api/package.json +++ b/api/package.json @@ -56,6 +56,7 @@ "csurf": "^1.11.0", "express": "^4.17.1", "express-session": "^1.17.2", + "helmet": "^5.0.2", "joi": "^17.4.2", "jsonwebtoken": "^8.5.1", "mongoose": "^6.0.12", diff --git a/api/src/app.ts b/api/src/app.ts index c7263d1..9c44224 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -7,6 +7,7 @@ import morgan from 'morgan' import cookieParser from 'cookie-parser' import dotenv from 'dotenv' import cors from 'cors' +import helmet from 'helmet' import { connectDB, @@ -37,6 +38,11 @@ export const cookieOptions = { ***********************************/ export const csrfProtection = csrf({ cookie: cookieOptions }) +/*********************************** + * Handle security and origin * + ***********************************/ +app.use(helmet()) + /*********************************** * Enabling CORS * ***********************************/ diff --git a/api/src/middlewares/authenticateToken.ts b/api/src/middlewares/authenticateToken.ts index 4d97583..716c1fd 100644 --- a/api/src/middlewares/authenticateToken.ts +++ b/api/src/middlewares/authenticateToken.ts @@ -1,11 +1,16 @@ import jwt from 'jsonwebtoken' +import { csrfProtection } from '../app' import { verifyTokenInDB } from '../utils' export const authenticateAccessToken = (req: any, res: any, next: any) => { + // if request is coming from web and has valid session + // we can validate the request and check for CSRF Token if (req.session?.loggedIn) { req.user = req.session.user - return next() + + return csrfProtection(req, res, next) } + authenticateToken( req, res, diff --git a/api/src/routes/web/index.ts b/api/src/routes/web/index.ts index 98b5a85..6f04db4 100644 --- a/api/src/routes/web/index.ts +++ b/api/src/routes/web/index.ts @@ -4,6 +4,13 @@ import webRouter from './web' const router = express.Router() -router.use('/', csrfProtection, webRouter) +router.use(csrfProtection) + +router.use(function (req, res, next) { + res.cookie('XSRF-TOKEN', req.csrfToken()) + next() +}) + +router.use('/', webRouter) export default router diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index 696b459..f9889b0 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -14,11 +14,6 @@ webRouter.get('/', async (_, res) => { return res.send('Web Build is not present') }) -webRouter.get('/form', function (req, res) { - // pass the csrfToken to the view - res.send({ csrfToken: req.csrfToken() }) -}) - webRouter.post('/login', async (req, res) => { const { error, value: body } = loginWebValidation(req.body) if (error) return res.status(400).send(error.details[0].message) diff --git a/web/src/components/login.tsx b/web/src/components/login.tsx index 7d8bfcd..cca13e2 100644 --- a/web/src/components/login.tsx +++ b/web/src/components/login.tsx @@ -10,13 +10,7 @@ const getAuthCode = async (credentials: any) => axios.post('/SASjsApi/auth/authorize', credentials).then((res) => res.data) const login = async (payload: { username: string; password: string }) => - axios.get('/form').then((res1) => - axios - .post('/login', payload, { - headers: { 'csrf-token': res1.data.csrfToken } - }) - .then((res2) => res2.data) - ) + axios.post('/login', payload).then((res) => res.data) const Login = ({ getCodeOnly }: any) => { const location = useLocation() diff --git a/web/src/context/appContext.tsx b/web/src/context/appContext.tsx index dea218e..6e5b72e 100644 --- a/web/src/context/appContext.tsx +++ b/web/src/context/appContext.tsx @@ -52,6 +52,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { }) .catch(() => { setLoggedIn(false) + axios.get('/') // get CSRF TOKEN }) }, []) From b4b60c69cf67a42f4797f7f1afe68b7a5eec2998 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sat, 30 Apr 2022 06:32:24 +0500 Subject: [PATCH 2/2] fix: setting CSRF Token for only rendering SPA --- api/src/routes/web/index.ts | 5 ----- api/src/routes/web/web.ts | 7 +++++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/api/src/routes/web/index.ts b/api/src/routes/web/index.ts index 6f04db4..e9d91cd 100644 --- a/api/src/routes/web/index.ts +++ b/api/src/routes/web/index.ts @@ -6,11 +6,6 @@ const router = express.Router() router.use(csrfProtection) -router.use(function (req, res, next) { - res.cookie('XSRF-TOKEN', req.csrfToken()) - next() -}) - router.use('/', webRouter) export default router diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index f9889b0..0a84af9 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -6,10 +6,13 @@ import { getWebBuildFolderPath, loginWebValidation } from '../../utils' const webRouter = express.Router() -webRouter.get('/', async (_, res) => { +webRouter.get('/', async (req, res) => { const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') - if (await fileExists(indexHtmlPath)) return res.sendFile(indexHtmlPath) + if (await fileExists(indexHtmlPath)) { + res.cookie('XSRF-TOKEN', req.csrfToken()) + return res.sendFile(indexHtmlPath) + } return res.send('Web Build is not present') })