Remaining Stateless - JWT + Cookies in Node JS(REST)
Tags: xss, csrf, jwt
Published: 2019-10-22 22:20:59

---
title: Remaining Stateless - JWT + Cookies in Node JS(REST)
published: true
description:
tags: XSS,CSRF,JWT
cover_image: https://thepracticaldev.s3.amazonaws.com/i/dr6e3at3dlznretj460y.jpeg
series: Remaining Stateless
---

JWT is stateless. Using cookies as a container to store JWT is easy and scalable.

## Why Store in cookies

The need to store JWT in cookies is seen in considering the difference between cross-site scripting(XSS) and cross-site request forgery (CSRF).
A little digging will suffice but in summary:-

 - XSS happens when an attacker inserts malicious code into the client-side browser basically by inputting a script in forms and checking if the browser will run these scripts.XSS exploits the trust a user has for a particular site.
 - CSRF, on the other hand, is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated(i.e they have sessions). CSRF attacks specifically target state-changing requests, not theft of data since the attacker has no way to see the response to the forged request.CSRF exploits the trust that a site has in a user's browser
 - XSS attacks occur in varied ways.
 - CSRF attack can only occur when an authenticated user session is hijacked, the attacker carrying out activities on behalf of the user.
 - Stateless JWT stored in the browser local storage is more susceptible to XSS attacks and less to CSRF attacks.
 - Cookies are less susceptible to XSS attacks provided it's HTTPOnly and the secure flag is set to true. Using only HTTPOnly might not prevent an attack as an attacker might use XST (cross-site tracing) to retrieve the cookie via XSS + HTTP Trace.
 - Cookies are more susceptible to CRSF attacks. However, it is well known how to mitigate CSRF attacks than the more varied XSS attacks.

## Storing JWT in cookies in Node JS 

#### Step 1 - Create a JWT on register or Login
- install JWT and dotenv

```javascript 
 npm install jsonwebtoken
 npm install dotenv
```
- Generate Token and save in token in Cookie


```javascript
const jwt = require('jsonwebtoken');
//import jwt from 'jsonwebtoken'; 

const generateToken = (res, id, firstname) => {
  const expiration = process.env.DB_ENV === 'testing' ? 100 : 604800000;
  const token = jwt.sign({ id, firstname }, process.env.JWT_SECRET, {
    expiresIn: process.env.DB_ENV === 'testing' ? '1d' : '7d',
  });
  return res.cookie('token', token, {
    expires: new Date(Date.now() + expiration),
    secure: false, // set to true if your using https
    httpOnly: true,
  });
};
module.exports = generateToken

// generateToken.js file
```

#### Step 2 - Use Cookie-Parser
- install cookie-parser and cors

```javascript
npm install cookie-parser
npm install cors

```

- Ensure server uses cookie-parser

```javascript


const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const loginRouter = require('./path/to/loginRouter');
require('dotenv').config();
const server = express();
server.use(express.json());
server.use(cors());
server.use(cookieParser());
// server should use login router
server.use('/', loginRouter)

const PORT = process.env.PORT || 5000;

server.listen(PORT, () => console.log(`Server started at port ${PORT}`));

// server.js file

```
#### Step 3- On Login/Register call the generate token


```javascript
const generateToken = require('./path/to/generateToken');

const login = async (req, res) => {
  const { email, password } = req.body;

  try {
// get user details based on the login parameters

    const result = await emailExists(email);

    const { id, firstname} = result;

    await generateToken(res, id, firstname);
// carry out other actions after generating token like sending a response);
  } catch (err) {
    return res.status(500).json(err.toString());
  }
};
// loginController.js file

```
### Verifying JWT stored in cookies

To create protected routes, the user needs to be authenticated. Authenticating a user requires decrypting the token gotten in the cookies. To do that you need an authentication middleware that gets the token from the cookies, verify the token is still valid and passes the action to the controller.

```javascript
const dotenv = require('dotenv');
const jwt = require('jsonwebtoken');

dotenv.config();
const verifyToken = async (req, res, next) => {
  const token = req.cookies.token || '';
  try {
    if (!token) {
      return res.status(401).json('You need to Login')
    }
    const decrypt = await jwt.verify(token, process.env.JWT_SECRET);
    req.user = {
      id: decrypt.id,
      firstname: decrypt.firstname,
    };
    next();
  } catch (err) {
    return res.status(500).json(err.toString());
  }
};

module.exports = verifyToken;

// verifyToken.js file
```
This is then used as a middleware to verify the user. If the user is verified, the controller is called. Otherwise, an authorization error is sent to the user

```javascript 
const express = require('express');
const user = require('./userController');
const verifyToken = require('../path/to/verifyToken');

const router = express.Router();

router.post(
  '/auth/verify',
  verifyToken,
  user.verifyMail,
);
)
module.exports = router;

// LoginRouter.js file

```

## Using backend generated Cookies for front-end authentication

When cookies are created at the backend with options of HTTPOnly set to true, the cookies are not visible to the frontend. When a request is made to the server, the cookies comes embedded in the headers alongside the request. 

- Set up cors on the backend

when using cookies on the backend, the origin of the request needs to be specifically stated. To do this ensure that the server has cors with the credentials set to true and origin containing the needed origins. One way to easily change up origin is by saving the frontend as an environment variable and setting it up using process.env.

```javascript
// server.js (backend)
server.use(
  cors({
    origin: [
      `${process.env.FRONT_URL}`,
      'http://localhost:3000',
      'https://mypage.com',
    ],
    credentials: true
  })
);

```

- Ensure headers are sent with request to the backend

On the frontend, when making request from the backend with axios or fetch ensure that headers are sent alongside the request. To do that with axios calls, set the with credentials option to true for both get and post calls. If you are using fetch calls set the credentials option to include likewise.

```javascript
// axios post
axios
    .post(`${url}`, {contentToBeSent}, {
      withCredentials: true
    })

// fetch get
fetch('https://example.com', {
  credentials: 'include'
});
```

I'll leave it to you to try JWT + Cookies for better security.


Feel free to reach out to me on [basil_cea](https://twitter.com/basil_cea) or on the threads below.