SET UP 2FA IN NODE.JS WITH SPEAKEASY
Tags: webdev, javascript, node, 2fa
Published: 2023-05-02 21:21:33

Adding 2FA to your application create an extra layer of security for your users, to protect their detail from scammers and hackers.

Passwords do not completely secure your application, infact, if the only layer of security on your application is password, your application is very vulnerable to scammers and hackers, because 6 in 10 people use the same password across all their accounts and platform, so access to the password, places the user at extreme risk if there's absence of an extra layer of security.

2FA is an extra layer of security to make sure only authorized personnel gain access to certain resources.

>  2FA might be in the form of security questions & answers, access token, biometric authentication and tokens.

So quickly we are going to be walking through 2FA in node.js using the _speakeasy library_

## REQUIREMENTS
Basic understanding of Javascript

_Speakeasy is a onetime passcode generator that supports Google authenticator and other two factor devices. [more info](https://snyk.io/advisor/npm-package/speakeasy)_

### STEP 1
initialize your project by running

```js
npm init
```

### STEP 2
Run this command to install needed dependencies

```js
npm install express mongoose dotenv body-parser speakeasy 
```

### STEP 3
Create an app.js file and then create an express app

```js
const express = require("express");
const dotenv = require("dotenv").config();
const bodyParser = require("body-parser");

const app = express();

app.use(bodyParser.json());
app.use(express.json());

const port = process.env.PORT || 3000;

app.listen(port, () => console.log(`server up and running at port ${port}`));
```

### STEP 4
Connect your express app to mongodb using the mongoose package installed earlier.

**NOTE**: Add your database connection string and password to the .env file

```js
const express = require("express");
const dotenv = require("dotenv").config();
const bodyParser = require("body-parser");
const mongoose = require("mongoose");

const app = express();

// middlewares
app.use(bodyParser);
app.use(express.json());

// connect to database
const db = process.env.DATABASE;

mongoose
  .connect(db, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => console.log("database connection successful"));

const port = process.env.PORT || 3000;

app.listen(port, () => console.log(`server up and running at port ${port}`));
```

### STEP 5
install `nodemon` globally to monitor changes in the app.js file

```js
npm install -g nodemon
```
then add this command to the scripts object inside your `package.json` file

```js
  "scripts": {
    "start": "nodemon app.js"
  },
```

### STEP 6
Open your terminal and npm start

```js
npm start
```

if you get something like this, then you're good to go, if not go through the codes again, probably you are making a mistake somewhere

```js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app.js`
server up and running at port 5000
database connection successful

```

### STEP 7
Create a model folder, and inside create a `token-model.js` file

_This file will be used to structure and store the secret string gotten from `speakeasy`_

inside the `token-model.js` file, paste in the following codes

```js
const mongoose = require("mongoose");

const tokenSchema = new mongoose.Schema(
  {
    secret: String,
    authIsSet: {
      type: Boolean,
      default: false,
    },
  },
  {
    toJSON: { virtuals: true },
    toObject: { virtuals: true },
  }
);

module.exports = mongoose.model("Token", tokenSchema);
```
### STEP 8
Create a controller folder, and inside create an `authentication-controller.js file`

Inside this file, import the Token model from the token-model.js file, import speakeasy and create a simple controller function

```js
const Token = require("../model/token-model");
const speakeasy = require("speakeasy");

exports.generateSecret = async (req, res, next) => {};
```

### STEP 9
Our 2FA process follows 3 flow

- Generate a secret key

- Generate a set-up key and paste in google authenticator app

- Authenticate the token for the first time

### GENERATE SECRET KEY
Run this command to generate a secret key, and save it to mongodb

```js
const Token = require("../model/token-model");
const speakeasy = require("speakeasy");

exports.generateSecret = async (req, res, next) => {
  const secretToken = speakeasy.generateSecret();
  try {
    const token = await Token.create({ secret: secretToken.base32 });
    return res.status(201).json({
      status: "success",
      message: "secret token generated",
      token: secretToken.base32
      tokenId: token.id,
    });
  } catch (error) {
    return res.status(400).json({
      status: "error",
      message: error.message || "something went wrong",
    });
  }
};
```

### GENERATE A SET-UP KEY
Generate a set-up key by pasting the secretToken.base32 returned from calling the `speakeasy.generateSecret()` command string inside your authenticator app

### STEP 10
Create a router folder and inside, create a `token-router.js` file and import express and create a simple router.

```js
const express = require("express");
const authenticationController = require("../controller/authentication-controller");

const router = express.Router();

router.post("/generate-secret", authenticationController.generateSecret);

module.exports = router;
```

import the router into your `app.js` file and register the route

```
app.use("/auth", authRouter);
```

### STEP 11
Now go to postman and fire this endpoint
`http://localhost:5000/auth/generate-secret`

You should get this response

```js
{
    "status": "success",
    "message": "secret token generated",
    "token": "H4XE22DMGZCD6NCHGJKC6JDSMFJV2T3EHF4DCUTJNNTGGYKWKNIQ"
    "tokenId": "68r77hdfbnnmdnmfdju8",
}
```

### STEP 12
Now copy and paste the token inside your google authenticator app, then you are ready to proceed to the final step, which is verifying the token for the first time.

## VERIFY THE TOKEN
Now we want to make sure the token on the client and server match.
After entering your set-up key, then enter the token you get to verify against the secret string that was generated in _step 9_.

Let us create a verifyToken function inside our controller and start the verification process

```js
exports.verifyToken = async (req, res, next) => {
  const secretToken = await Token.findById(req.params.id);
  const enteredToken = req.body.token;

  const verified = speakeasy.totp.verify({
    secret: secretToken.token.secret,
    encoding: "base32",
    token: enteredToken,
  });
  if (!verified) {
    return res.status(403).json({
      message: "verification failed",
    });
  }
await secretToken.authIsSet = true

  return res.status(200).json({
    status: "success",
    message: "verification successful",
    verified: verified,
  });
};
```
### STEP 13
Create a `verify-token` route to fire this request

```js
router.post("/verify-token/:id", authenticationController.verifyToken);
```
if after firing this endpoint, you get this response, then you have successfully added 2FA to your application!.
**CONGRATULATIONS**

```js
{
    "status": "success",
    "message": "verification successful",
    "verified": true
}
```

I hope you found this write up helpful!.
Checkout my codes here [github](https://github.com/MrCandie/2FA)

Let me know your thoughts in the comment section