Implementing 2 Factor Authentication using Authy
Tags: 2fa, authy, express, tutorial
Published: 2020-06-02 03:37:30

Hey there :wave:

## So what is 2FA?
The traditional way of user authentication involved the user sending an email and password which are then verified after which the user is logged in. This brings about a set of potential risks such as hacking, weak passwords, passwords reuse and many more.
2 factor authentication provides an extra step that the user has to complete before being authenticated.
This article aims at showing you how to use Authy from Twilio in your node API so as to implement 2FA.
We will be sending a code to the user's cell during sign-in which they have to send back to the API for verification.

### Having said that, let's dive in

Find the completed source code of this application [here](https://github.com/gathoni-k/implement-2fa.git)

#### Requirements
For this project, you'll have to be know a little bit of JavaScript and working with node
Have node and npm in you machine. If not you can get it [here](https://nodejs.org/en/download/)

#### Get an API key from the Authy dashboard.
Head over to the [Authy dashboard](https://dashboard.authy.com/signin) and sign-in either with a Twilio account or using Authy credentials.
After entering your Authy token you'll be redirected to your dashboard where you can create a new application.
* Click on the New Application button at the bottom of the navigation menu
* Enter your preferred app name in the pop-up dialog window and hit create
* Grab the API key for production, we'll use it later.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zl9g0zj6akbco5102cd2.png)

Create a folder and name it according to you preference, move into it and in a terminal initialize npm 
```bash
npm init
```
This will generate a package.json file for you.
#### Install dependencies
```bash
npm i express, mongoose,bcryptjs, authy, dotenv
```
Create a .env file and add the port number.

#### Create routes
In route.js,
```JavaScript
const router = require('express').Router();
router.get( '/ping', async (req, res) => {
  res.send('pong')
});
module.exports = router;
```
This is a route to test if our app is working


#### Start server
In app.js
```JavaScript
const express = require('express');
require('dotenv').config();
const routes = require('./routes');

const app = express();

app.use(express.json());
app.use('/user', routes)

const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
    console.log(`Listening on ${PORT}`)
});
```
To run the app:
```bash
node app
```
You should see the following in the terminal:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bed5orh1iyxkdf2w4be2.png)
Testing this route:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/urwazx7l456ku3fw080g.png)

Now that we're sure the app works just fine, we can continue
#### Connect database
In db.js, add the following code
We're using a database so that we can work with persistent data.

```JavaScript
const mongoose = require('mongoose');
require('dotenv').config();

mongoose.connect(process.env.MONGO_URI, {useNewUrlParser: true, useUnifiedTopology: true});

const db = mongoose.connection;

db.once('open', () => {
    console.log('MMongoose default connection open');
});

db.on('error', () => {
    console.log('An error occurred while connectiong to database');
});
```
To connect the database, you will have to import db.js in app.js just after importing your other packages

#### Define the user model
In model.js, add the following code
```JavaScript
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const { Schema } = mongoose;

const userSchema = new Schema({
    email: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true
    },
    authyId: {
        type: String,
        default: null
    }
});

userSchema.pre('save', async function(next) {
    try {
        if (!this.isModified('password')) next();
        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(this.password, salt);
        this.password = hashedPassword;
        next();
    } catch (error) {
        return next(error);
    }
});

userSchema.methods.comparePassword = function(plainPwd) {
return bcrypt.compareSync(plainPwd, this.password);
};

module.exports = mongoose.model('User', userSchema);
```
You may have noticed the authyId value, this is to store the id sent back by Authy after the user is registered. This id is shared across all sites that use that phone number, convenient right?


#### Create user account: sign up route
Add this code to route.js
```JavaScript
router.post( '/signup', async (req, res) => {
    try {
        const { email, password } = req.body;
        const exists = await User.findOne({ email });
        if (exists) {
            return res.json({ message: "Email exists"});
        }
        const newUser = new User({
            email,
            password
        });
        await newUser.save();
        res.status(200).json({ message: "User account registered"})
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
});
```
The above code creates a user using a unique email and a password.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/noagaaio0u78ky9hizae.png)

#### Enable 2 factor authentication
Before adding code to enable 2fa, we'll have to initialize Authy first.

Insert the following code above your routes code
```JavaScript
require('dotenv').config();
const authy = require('authy')(process.env.AUTHY_API_KEY);
```

Then create the route
```JavaScript
router.post('/enable/2fa', async (req, res) => {
    try {
        const { email, countryCode, phone } = req.body;
        const user = await User.findOne({ email });
        if (!user) {   
            return res.json({ message: 'User account does not exist' });
        }
        authy.register_user(email, phone, countryCode, (err, regRes) =>{
            if (err) {
                return res.json({ message: 'An error occurred while registering user' });
            }
            user.authyId = regRes.user.id;
            user.save((err, user) => {
                if (err) {
                    return res.json({ message: 'An error occurred while saving authyId into db' });
                }
            })
        });
        res.status(200).json({ message: '2FA enabled' });
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
});

```
Let's take a closer look at the above code. To register a user Authy requires the email, phone number and country code of the  user.
First we have to confirm if that user account exists and if doesn't we'll send an error message.
Then call authy.register_user passing the user's credentials, this function returns a registered user whose id we store in the database.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/z389t6k46w96227ldtxt.png)
#### Sign in route
For this route, we want to be able to handle a sign in using only the email and password, for users who haven't enabled 2fa and also enforce 2fa sign in for those who've enabled it.
To check whether the user has enabled 2fa we'll check if their authyId from the database is set to null, if it is, we'll log them in using an email and password only and if otherwise send an OTP to their cell.

Add the following route in route.js
```JavaScript
router.post('/signin', async (req, res) => {
    try {
        const { email, password } = req.body;
        const user = await User.findOne({ email });
        const isMatch = await user.comparePassword(password);
        if (!user || !isMatch) {
            return res.json({ message: "Email or password incorrect"});
        }
        if (user.authyId) {
            authy.request_sms(
                user.authyId, {force: true},  
                function (err, smsRes) {
                    if (err) {
                        return res.json({ message: 'An error occurred while sending OTP to user' });
                    } 
            });
            return res.status(200).json({ message: 'OTP sent to user' });
        }
        res.status(200).json({ message: "Logged in successfully"})
    } catch (error) {
        res.status(500).json({ message: error.message })
    }
});

```
If user has not enabled 2fa:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/w4u95t3yd6g2us92zfce.png)
If user has enabled 2fa:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mqnmu9v19atgi4zzei21.png)
#### Verification route
To verify the OTP sent to the user's cell, we  call authy's verify function and pass to it the user id and the OTP.
If no errors occur, the user has now authenticated with a second factor and is logged in.


```JavaScript
router.post('/verify/:token', async (req, res) => {
    try {
        const { email } = req.body
        const user = await User.findOne({ email });
        authy.verify(
            user.authyId,
            req.params.token,
            function(err, tokenRes){
                if (err) {
                    res.json({ message: 'OTP verification failed'});
                }
                res.status(200).json({ message: 'Token is valid'});
            });
    } catch (error) {
        res.status(500).json({ message: error.message});
    }
});
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2ch2w8u146v7hfg7b758.png)

To recap, the main key things are
* Get an API key from Authy's dashboard.
* Register a user uswing authy's register_user function.
* Request an sms to be sent to the cell of the registered user.
* Verify the code received by the user.

You have just implemented 2 Factor Authentication using Node.js and Authy.:clap:

Find the completed source code of this application [here](https://github.com/gathoni-k/implement-2fa.git)