Login App with CSRF protection – Implement authentication in Node.js using JWT access token and refresh token – Part 2
In this article, we will show you how to implement authentication in Node.js using JWT access token and refresh token. As we have already discussed about the implementation flow of the authentication a.k.a secure login app with CSRF protection in the previous article. So we will cover only Node.js implementation in this second part of the series.
Node.js API Authentication With JWT, Add Login Using the Authorization Code Flow, Refresh Tokens With JWT Authentication, JWT Refresh Token for Multiple Devices, token based authentication in node js example, bearer token build a node.js api authentication with jwt tutorial, step by step explanation of the authentication in Node js using JWT access token, refresh token and CSRF token. JWT authentication in backend technologies like laravel, php, etc, Requesting access tokens and authorization codes. Secure authentication in node js using JWT access token, refresh token, CSRF protection and XSS protection.
We had already discussed the authentication with Node.js in the previous articles where we explained the basic way to manage the authentication using JWT token and moreover here we will learn about the security.
Checkout more articles on Node.js
We planned to divide this article into three parts.
- Part 1 – Understanding authentication using JWT access token and refresh token with CSRF protection
- Part 2 – Implement authentication in Node.js using JWT token with CSRF protection (You are here…)
- Part 3 – Implement authentication in ReactJS using REST API with CSRF protection
Way to create secure REST API in Node.js using JWT token with CSRF protection
- Create simple REST API in Node.js
- Install npm dependencies
- Define the environment variables
- Manage general utility
- Create API for user sign in
- Create API for user sign out
- Create API to verify token
- Create API to get user list
- Implement middleware to validate the token
- Output
1. Create simple REST API in Node.js
As we are planning to create a REST API in Node.js so we should have an initial setup of the Node.js application. Checkout this link for your reference.
Below is the file structure of the application that you should prefer.
2. Install npm dependencies
We should have the following dependencies in the Node application.
- dotenv – It helps us to load the
.env
variables intoprocess.env
. With the help of it we can access those variables throughout the application. - cors– To access resources from the different server, we have to enable the Cross-Origin Resource Sharing (CORS) request.
- jsonwebtoken– We’ll use this package to create access token and refresh token by passing the primary JSON data and secret key.
- body-parser– It used to parse the incoming request and make it available under req.body property.
- cookie-parser – This package is used to parse the cookies and also used to enable the signed cookie support by passing a secret key.
- rand-token – Additionally we will use this package to create a random token for CSRF protection.
- moment – Here we’ll use the moment npm package to set the expiry date of the token.
- ms – Use this package to convert the time format to milliseconds.
Run the following command to install the required dependencies.
1 | npm i dotenv cors jsonwebtoken body-parser cookie-parser rand-token moment ms |
After successful installation, We will use these packages in several files for different functionalities. For now your server.js
file should look like this and later on we’ll see the utils.js
file.
server.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | require('dotenv').config(); const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const app = express(); const port = process.env.PORT || 4000; // enable CORS app.use(cors({ origin: 'http://localhost:3000', // url of the frontend application credentials: true // set credentials true for secure httpOnly cookie })); // parse application/json app.use(bodyParser.json()); // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: true })); // use cookie parser for secure httpOnly cookie app.use(cookieParser(process.env.COOKIE_SECRET)); app.listen(port, () => { console.log('Server started on: ' + port); }); |
In above code, we have added only those packages that are used in this file only and rest of the packages we will add it in utils.js
file.
If you noticed that we have set http://localhost:3000
to origin in configuration of the CORS package that means we will allow only this url to access our API. Also we will use secure HttpOnly cookies for authentication so we need to set credentials: true
in the same configuration.
For secure HttpOnly cookies, we have to use a secret key for cookie parsing. Here we are assigning the cookie secret from the environment file that we covered in the next topic.
3. Define the environment variables
Let’s try to create a .env
file to define the environment variables and access it through process.env
.
.env
1 2 3 4 5 | JWT_SECRET=ABCDEF$123 ACCESS_TOKEN_LIFE=15m REFRESH_TOKEN_LIFE=30d COOKIE_SECRET=XYZABC$123 NODE_ENV=development |
Following keys to use in the project.
- JWT_SECRET – Use it to create JWT access token and refresh token.
- ACCESS_TOKEN_LIFE – Define the life of the access token so we can use it to set the expiry time of the access token. We have considered the 15 mins (15m).
- REFRESH_TOKEN_LIFE – Same as the access token we will define the life of the refresh token. It should be longer than the access token so we have considered the 30 days (30d).
- COOKIE_SECRET – We’ll use this secret key for cookie parsing as we discussed in the above topic.
- NODE_ENV – It’s used to set the environment of the project.
To use all these variables, we have to add require('dotenv').config();
at the top of the server.js
file that we already added in above server.js
file code.
4. Manage general utility
Now it’s time to create the several functions that help us to create an authentication API. We have listed the following utilities.
generateToken
This function will be used to create an access token, CSRF token and expiry time with the help of the jsonwebtoken, rand-token and moment npm packages.
In the first step, we will create a plain object to generate an access token. We will also create a CSRF token in the same function so that can be used to create a private key for the access token.
Let’s create a private key by combining the CSRF or XSRF token and JWT secret key that is defined in the
.env
file. CSRF token will help us to identify the real user when someone calls the API from the frontend side.So now we are able to create an access token with the help of the plain object and the private key. Also we will set the life of the access token.
At last we have to create a variable that contains the expiry time of the token which will pass in the API response.
123456789101112131415161718192021222324252627282930313233343536const jwt = require('jsonwebtoken');const moment = require('moment');const randtoken = require('rand-token');const ms = require(‘ms’);// generate tokens and return itfunction generateToken(user) {//1. Don't use password and other sensitive fields//2. Use the information that are useful in other partsif (!user) return null;const u = {userId: user.userId,name: user.name,username: user.username,isAdmin: user.isAdmin};// generat xsrf token and use it to generate access tokenconst xsrfToken = randtoken.generate(24);// create private key by combining JWT secret and xsrf tokenconst privateKey = process.env.JWT_SECRET + xsrfToken;// generate access token and expiry dateconst token = jwt.sign(u, privateKey, { expiresIn: process.env.ACCESS_TOKEN_LIFE });// expiry time of the access tokenconst expiredAt = moment().add(ms(process.env.ACCESS_TOKEN_LIFE), 'ms').valueOf();return {token,expiredAt,xsrfToken}}We’ll call this function to create an access token and CSRF token before it’s expired. For us we have to generate tokens in every 15 mins.
generateRefreshToken
We will create another function that will return the refresh token using JWT. We will consider the user id, JWT secret and expiry time to create a refresh token.
123456// generate refresh tokenfunction generateRefreshToken(userId) {if (!userId) return null;return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: process.env.REFRESH_TOKEN_LIFE });}Here we will not use the XSRF token as a private key to generate refresh token because XSRF and access token are required to generate in short time but refresh token have a longer life than access token.
verifyToken
Let’s create a function that will be used to verify the both access and refresh token. We will use
xsrfToken
as optional parameters to create a private key for both tokens.12345// verify access token and refresh tokenfunction verifyToken(token, xsrfToken, cb) {const privateKey = process.env.JWT_SECRET + xsrfToken;jwt.verify(token, privateKey, cb);}We will pass the empty string for
xsrfToken
to verify the refresh token. We should have a callback function to process the further request that will be received via the third parameter.getCleanUser
As per the previous article of the authentication REST API, We also need a simple function that returns the basic user information so we call pass it to the user in the API response.
1234567891011// return basic user detailsfunction getCleanUser(user) {if (!user) return null;return {userId: user.userId,name: user.name,username: user.username,isAdmin: user.isAdmin};}handleResponse
Let’s create a function that helps us to create the response based on status code and some other required details.
123456789101112131415161718192021222324252627282930// handle the API responsefunction handleResponse(req, res, statusCode, data, message) {let isError = false;let errorMessage = message;switch (statusCode) {case 204:return res.sendStatus(204);case 400:isError = true;break;case 401:isError = true;errorMessage = message || 'Invalid user.';clearTokens(req, res);break;case 403:isError = true;errorMessage = message || 'Access to this resource is denied.';clearTokens(req, res);break;default:break;}const resObj = data || {};if (isError) {resObj.error = true;resObj.message = errorMessage;}return res.status(statusCode).json(resObj);}clearTokens
At last, we need a function that helps us to clear the tokens from the memory and cookie.
Here we defined the variable
refreshTokens
to manage the CSRF or XSRF token that links with refresh token in the form of the object. You can also manage it on redis server.Also we will have a cookie option to create a secure HttpOnly cookie for refresh token and the same option will be used to remove token from the cookie. You can set the domain as well.
To access a signed cookie, we have to use the
signedCookies
object ofreq
.123456789101112131415161718192021const dev = process.env.NODE_ENV !== 'production';// refresh token list to manage the xsrf tokenconst refreshTokens = {};// cookie options to create refresh tokenconst COOKIE_OPTIONS = {// domain: "localhost",httpOnly: true,secure: !dev,signed: true};// clear tokens from cookiefunction clearTokens(req, res) {const { signedCookies = {} } = req;const { refreshToken } = signedCookies;delete refreshTokens[refreshToken];res.clearCookie('XSRF-TOKEN');res.clearCookie('refreshToken', COOKIE_OPTIONS);}
Let’s combine all code together and see how it looks.
utils.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | const jwt = require('jsonwebtoken'); const moment = require('moment'); const randtoken = require('rand-token'); const ms = require('ms'); const dev = process.env.NODE_ENV !== 'production'; // refresh token list to manage the xsrf token const refreshTokens = {}; // cookie options to create refresh token const COOKIE_OPTIONS = { // domain: "localhost", httpOnly: true, secure: !dev, signed: true }; // generate tokens and return it function generateToken(user) { //1. Don't use password and other sensitive fields //2. Use the information that are useful in other parts if (!user) return null; const u = { userId: user.userId, name: user.name, username: user.username, isAdmin: user.isAdmin }; // generat xsrf token and use it to generate access token const xsrfToken = randtoken.generate(24); // create private key by combining JWT secret and xsrf token const privateKey = process.env.JWT_SECRET + xsrfToken; // generate access token and expiry date const token = jwt.sign(u, privateKey, { expiresIn: process.env.ACCESS_TOKEN_LIFE }); // expiry time of the access token const expiredAt = moment().add(ms(process.env.ACCESS_TOKEN_LIFE), 'ms').valueOf(); return { token, expiredAt, xsrfToken } } // generate refresh token function generateRefreshToken(userId) { if (!userId) return null; return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: process.env.REFRESH_TOKEN_LIFE }); } // verify access token and refresh token function verifyToken(token, xsrfToken = '', cb) { const privateKey = process.env.JWT_SECRET + xsrfToken; jwt.verify(token, privateKey, cb); } // return basic user details function getCleanUser(user) { if (!user) return null; return { userId: user.userId, name: user.name, username: user.username, isAdmin: user.isAdmin }; } // handle the API response function handleResponse(req, res, statusCode, data, message) { let isError = false; let errorMessage = message; switch (statusCode) { case 204: return res.sendStatus(204); case 400: isError = true; break; case 401: isError = true; errorMessage = message || 'Invalid user.'; clearTokens(req, res); break; case 403: isError = true; errorMessage = message || 'Access to this resource is denied.'; clearTokens(req, res); break; default: break; } const resObj = data || {}; if (isError) { resObj.error = true; resObj.message = errorMessage; } return res.status(statusCode).json(resObj); } // clear tokens from cookie function clearTokens(req, res) { const { signedCookies = {} } = req; const { refreshToken } = signedCookies; delete refreshTokens[refreshToken]; res.clearCookie('XSRF-TOKEN'); res.clearCookie('refreshToken', COOKIE_OPTIONS); } module.exports = { refreshTokens, COOKIE_OPTIONS, generateToken, generateRefreshToken, verifyToken, getCleanUser, handleResponse, clearTokens } |
5. Create API for user sign in
Before we create an API, we can create a static list of the users that used to validate the user’s credential. In your case it should be a database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // list of the users to be consider as a database for example const userList = [ { userId: "123", password: "clue", name: "Clue", username: "clue", isAdmin: true }, { userId: "456", password: "mediator", name: "Mediator", username: "mediator", isAdmin: true }, { userId: "789", password: "123456", name: "Clue Mediator", username: "cluemediator", isAdmin: true } ] |
We will also add all dependencies in server.js
file from the utils.js
file.
1 2 3 4 | const { refreshTokens, COOKIE_OPTIONS, generateToken, generateRefreshToken, getCleanUser, verifyToken, clearTokens, handleResponse, } = require('./utils'); |
Let’s create an API for sign-in where users can get authenticated by passing the username and password. On successful authentication, we will return the access token and expiry time along with the user details.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | // validate user credentials app.post('/users/signin', function (req, res) { const user = req.body.username; const pwd = req.body.password; // return 400 status if username/password is not exist if (!user || !pwd) { return handleResponse(req, res, 400, null, "Username and Password required."); } const userData = userList.find(x => x.username === user && x.password === pwd); // return 401 status if the credential is not matched if (!userData) { return handleResponse(req, res, 401, null, "Username or Password is Wrong."); } // get basic user details const userObj = getCleanUser(userData); // generate access token const tokenObj = generateToken(userData); // generate refresh token const refreshToken = generateRefreshToken(userObj.userId); // refresh token list to manage the xsrf token refreshTokens[refreshToken] = tokenObj.xsrfToken; // set cookies res.cookie('refreshToken', refreshToken, COOKIE_OPTIONS); res.cookie('XSRF-TOKEN', tokenObj.xsrfToken); return handleResponse(req, res, 200, { user: userObj, token: tokenObj.token, expiredAt: tokenObj.expiredAt }); }); |
In the above code we have added the validation for user credentials and create a plain object from user data. Also we are creating the access token, refresh token and CSRF/XSRF token with the help of the previously created function.
We will store the XSRF token in the variable refreshTokens
that link with the refresh token in the form of the object. Also we will set XSRF and refresh tokens into the cookie so we can pass it along with the API response.
In the subsequent private requests, we will use all of the tokens (access token, refresh token and XSRF token) for verification when it is required.
6. Create API for user sign out
Now let’s create an API to manage the user logout. In this API, we have to simply call a function to clear tokens.
1 2 3 4 5 | // handle user logout app.post('/users/logout', (req, res) => { clearTokens(req, res); return handleResponse(req, res, 204); }); |
7. Create API to verify token
Let’s have another API to verify the token. This API will be used to manage the silent authentication. Please refer to the link to get more idea about the silent authentication.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | // verify the token and return new tokens if it's valid app.post('/verifyToken', function (req, res) { const { signedCookies = {} } = req; const { refreshToken } = signedCookies; if (!refreshToken) { return handleResponse(req, res, 204); } // verify xsrf token const xsrfToken = req.headers['x-xsrf-token']; if (!xsrfToken || !(refreshToken in refreshTokens) || refreshTokens[refreshToken] !== xsrfToken) { return handleResponse(req, res, 401); } // verify refresh token verifyToken(refreshToken, '', (err, payload) => { if (err) { return handleResponse(req, res, 401); } else { const userData = userList.find(x => x.userId === payload.userId); if (!userData) { return handleResponse(req, res, 401); } // get basic user details const userObj = getCleanUser(userData); // generate access token const tokenObj = generateToken(userData); // refresh token list to manage the xsrf token refreshTokens[refreshToken] = tokenObj.xsrfToken; res.cookie('XSRF-TOKEN', tokenObj.xsrfToken); // return the token along with user details return handleResponse(req, res, 200, { user: userObj, token: tokenObj.token, expiredAt: tokenObj.expiredAt }); } }); }); |
In the above code, we are trying to get the refresh token from the cookies. If it’s not exist then we simply return the no content success response.
In the next step, we have to verify the XSRF token to prevent the CSRF attack.
After validating XSRF token, we will try to verify the refresh token with the help of the predefined function. Here we passed the xsrfToken as an empty string into the function because we have generated refresh token without XSRF token.
On successful validation, we have to return the user details, access token and expiry time into the API response.
No need to generate a new refresh token in this API request.
8. Create API to get user list
In this API, We have to created a private API that will return the list of the users. Without an access token you can not get this list. We have added authMiddleware
middleware to validate the route. In the next point, we will explain about the middleware.
1 2 3 4 5 6 7 8 9 | // get list of the users app.get('/users/getList', authMiddleware, (req, res) => { const list = userList.map(x => { const user = { ...x }; delete user.password; return user; }); return handleResponse(req, res, 200, { random: Math.random(), userList: list }); }); |
Additionally, we have removed the password from the list before sending it via API response.
9. Implement middleware to validate the token
The middleware is the key part of the article to verify the private routes. It used to verify the access token and CSRF token from the request header.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // middleware that checks if JWT token exists and verifies it if it does exist. // In all private routes, this helps to know if the request is authenticated or not. const authMiddleware = function (req, res, next) { // check header or url parameters or post parameters for token var token = req.headers['authorization']; if (!token) return handleResponse(req, res, 401); token = token.replace('Bearer ', ''); // get xsrf token from the header const xsrfToken = req.headers['x-xsrf-token']; if (!xsrfToken) { return handleResponse(req, res, 403); } // verify xsrf token const { signedCookies = {} } = req; const { refreshToken } = signedCookies; if (!refreshToken || !(refreshToken in refreshTokens) || refreshTokens[refreshToken] !== xsrfToken) { return handleResponse(req, res, 401); } // verify token with secret key and xsrf token verifyToken(token, xsrfToken, (err, payload) => { if (err) return handleResponse(req, res, 401); else { req.user = payload; //set the user to req so other routes can use it next(); } }); } |
If we will not receive the token then we can consider as unauthorized user. If a token exists then try to get the XSRF token and verify it to avoid CSRF attack. After that we need to verify the token with the help of XSRF token.
If a user gets authenticated using access token and CSRF token then attach the user object in the same request so we can get the object in next routes.
10. Output
We have already provided the .env
and utils.js
files so now let’s combine all of the above code together and see the server.js
file.
server.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | require('dotenv').config(); const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const { refreshTokens, COOKIE_OPTIONS, generateToken, generateRefreshToken, getCleanUser, verifyToken, clearTokens, handleResponse, } = require('./utils'); const app = express(); const port = process.env.PORT || 4000; // list of the users to be consider as a database for example const userList = [ { userId: "123", password: "clue", name: "Clue", username: "clue", isAdmin: true }, { userId: "456", password: "mediator", name: "Mediator", username: "mediator", isAdmin: true }, { userId: "789", password: "123456", name: "Clue Mediator", username: "cluemediator", isAdmin: true } ] // enable CORS app.use(cors({ origin: 'http://localhost:3000', // url of the frontend application credentials: true // set credentials true for secure httpOnly cookie })); // parse application/json app.use(bodyParser.json()); // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: true })); // use cookie parser for secure httpOnly cookie app.use(cookieParser(process.env.COOKIE_SECRET)); // middleware that checks if JWT token exists and verifies it if it does exist. // In all private routes, this helps to know if the request is authenticated or not. const authMiddleware = function (req, res, next) { // check header or url parameters or post parameters for token var token = req.headers['authorization']; if (!token) return handleResponse(req, res, 401); token = token.replace('Bearer ', ''); // get xsrf token from the header const xsrfToken = req.headers['x-xsrf-token']; if (!xsrfToken) { return handleResponse(req, res, 403); } // verify xsrf token const { signedCookies = {} } = req; const { refreshToken } = signedCookies; if (!refreshToken || !(refreshToken in refreshTokens) || refreshTokens[refreshToken] !== xsrfToken) { return handleResponse(req, res, 401); } // verify token with secret key and xsrf token verifyToken(token, xsrfToken, (err, payload) => { if (err) return handleResponse(req, res, 401); else { req.user = payload; //set the user to req so other routes can use it next(); } }); } // validate user credentials app.post('/users/signin', function (req, res) { const user = req.body.username; const pwd = req.body.password; // return 400 status if username/password is not exist if (!user || !pwd) { return handleResponse(req, res, 400, null, "Username and Password required."); } const userData = userList.find(x => x.username === user && x.password === pwd); // return 401 status if the credential is not matched if (!userData) { return handleResponse(req, res, 401, null, "Username or Password is Wrong."); } // get basic user details const userObj = getCleanUser(userData); // generate access token const tokenObj = generateToken(userData); // generate refresh token const refreshToken = generateRefreshToken(userObj.userId); // refresh token list to manage the xsrf token refreshTokens[refreshToken] = tokenObj.xsrfToken; // set cookies res.cookie('refreshToken', refreshToken, COOKIE_OPTIONS); res.cookie('XSRF-TOKEN', tokenObj.xsrfToken); return handleResponse(req, res, 200, { user: userObj, token: tokenObj.token, expiredAt: tokenObj.expiredAt }); }); // handle user logout app.post('/users/logout', (req, res) => { clearTokens(req, res); return handleResponse(req, res, 204); }); // verify the token and return new tokens if it's valid app.post('/verifyToken', function (req, res) { const { signedCookies = {} } = req; const { refreshToken } = signedCookies; if (!refreshToken) { return handleResponse(req, res, 204); } // verify xsrf token const xsrfToken = req.headers['x-xsrf-token']; if (!xsrfToken || !(refreshToken in refreshTokens) || refreshTokens[refreshToken] !== xsrfToken) { return handleResponse(req, res, 401); } // verify refresh token verifyToken(refreshToken, '', (err, payload) => { if (err) { return handleResponse(req, res, 401); } else { const userData = userList.find(x => x.userId === payload.userId); if (!userData) { return handleResponse(req, res, 401); } // get basic user details const userObj = getCleanUser(userData); // generate access token const tokenObj = generateToken(userData); // refresh token list to manage the xsrf token refreshTokens[refreshToken] = tokenObj.xsrfToken; res.cookie('XSRF-TOKEN', tokenObj.xsrfToken); // return the token along with user details return handleResponse(req, res, 200, { user: userObj, token: tokenObj.token, expiredAt: tokenObj.expiredAt }); } }); }); // get list of the users app.get('/users/getList', authMiddleware, (req, res) => { const list = userList.map(x => { const user = { ...x }; delete user.password; return user; }); return handleResponse(req, res, 200, { random: Math.random(), userList: list }); }); app.listen(port, () => { console.log('Server started on: ' + port); }); |
Now let’s check these APIs in postman. Below we have provided you a collection of the APIs. If you don’t get an idea then read the description in the postman API collection.
That’s it for today. Thank you for reading.
In the next article, we’ll implement authentication in ReactJS using REST API with CSRF protection.
Like share and follow us. Happy Coding..!!
Hei very nice article. I think there is one mistake in your article. In the overview of the server.js you have forogt the authmiddleware.
This is the code from the complete server.js
// get list of the users
app.get(‘/users/getList’, (req, res) => {
if (!req.user)
return handleResponse(req, res, 401);
const list = userList.map(x => {
const user = { …x };
delete user.password;
return user;
});
return handleResponse(req, res, 200, { random: Math.random(), userList: list });
});
and this ist the code from the methode.
// get list of the users
app.get(‘/users/getList’, authMiddleware, (req, res) => {
const list = userList.map(x => {
const user = { …x };
delete user.password;
return user;
});
return handleResponse(req, res, 200, { random: Math.random(), userList: list });
});
Hello Tobias,
I forgot to update the code in the article. Both codes will work but the new code is better than the previous one for large scale application.
I have updated the new code.
Thank you for drawing our attention.
Let us know if you are facing any difficulties.
How do you protect yourself from login CSRF? An attacker could enter his information without the consent of the host and watch the activity of the host after. How would you prevent that?
I really appreciate an answer of yours 🙂
Btw, your articles are well made. It’s hard to find content right now that don’t use localstorage as a way to manage token. You are giving a full example of how to made it compare to others who tend to implement partially or in an abstract way that is hard to get for beginner in web technology.
Hi Karl,
In the CSRF attack, the attacker can not read the cookie because of the cross-site domain. BTW here we have implemented the demo of the login where we implemented CSRF protection after the login. You can also manage it throughout the application. It’s all depends on you that how would you like to set up your project architecture.
BTW thank you for your kind words!
After a lot of hard work and searching, we are able to write this article. If you have any feedback for this approach (good and bad), do let us know in the comments. So we can share it with all other readers.
Thank you!
Just found a goldmine. I do really appreciate the valuable resources you guys have made on this place.
Hi Jun,
We are glad you liked it.
Subscribe us for weekly updates or like and follow us for regular updates.
Happy Coding..!!
Hi Clue Mediator,
I appreciate this article! This really helped! I do have a question though — if I have another API on another server that I also want to secure. How do I ensure that with that other API, only users authenticated through this API are allowed?
You can write the middleware to secure your API using the JWT token.
Am I allowed to use this for my application?
Yes, you can use it anytime and anywhere as all contents are free to use. You can use Git Repository for a quick demo.
Subscribe us for weekly updates or like and follow us for regular updates.
Heey i was wondering if you have any examples of redis implementation on the backend instead of list in memory
Hi Nikola,
We don’t have any redis implementation yet but in the future, we will work on it.
hello and thank you for your great article
A question crossed my mind:
When a refresh token is verified and it was expired we send 401 but the expired refresh token is still in the refreshTokens variable, so refreshTokens get bigger and bigger with expired refresh tokens which are not needed anymore! Is it correct or I’m just confused?
Hi Moj, How you manage the store is up to you but yes it is a good idea to clear the token when it is not needed.
Hi – by far the best article to demonstrate the XSRF that I have found – kudos.
QQ: why did you set the “XSRF-TOKEN” in the cookie?
res.cookie(“XSRF-TOKEN”, tokenObj.xsrfToken);
Isn’t the refresh token enough?
I did not get this part.
We need to add it for CSRF (Cross-Site Request Forgery) protection. You can also check the whole implementation flow.