Back to Blog

Quick and Easy 2FA: Adding Authy to a NodeJS App

2016-11-14_1925When first discussing Authy with potential customers, one of my favorite points of discussion is how easy it is to add our product to a pre-existing infrastructure.  Typically we see engineering teams adding Authy within a couple of days (or a few story points if Agile).  Aside from the ease-of-implementation, having a second-factor platform that can 1) deliver globally and 2) scale immensely is a great boon for startups and multinationals alike.

While I can talk about the ease of implementation all day long, it is honestly a bit simpler to demonstrate it.  If you’d rather just check out the code, you can clone our very simple Authy 2FA and Phone Verification app available on Github.

Authy Dashboard Registration

Before using Authy, you will need to register with Twilio and get an Authy API key from the Authy dashboard.   As part of the dashboard setup process, you’ll need to install the Authy app and confirm access with an Authy TOTP token.

You can sign up for Authy here.

Login Flow

The classic authentication approach simply requires a user to enter a username and password, verify the password is correct and then create a session for the user.  

Single Factor (Password) Authentication

login-no-2fa
This single factor has a large number of issues, unfortunately.   Password re-use, hacked databases, poorly encrypted passwords, spear-phishing and general social hacking all plague these kinds of sites.  

If we introduce Authy as a second factor, the flow changes a bit as we now require an additional step before authenticating a user.

login-with-2fa-flow
You can view how these flows are implemented below in the section titled “Protecting Routes with Two Factors.”

User Model

When you register for 2FA with Authy the first time, we create a unique Authy ID and associate it with your registering phone number and country code.  This Authy ID will persist across all sites which take advantage of our 2FA solution.

Extending User Schema to support Authy

var UserSchema = new Schema({
    username: {
         type: String
    }, 
    email: {
         type: String
    }, 
    hashed_password: String
});

Extended with the AuthyID

var UserSchema = new Schema({
    username: {
         type: String
    }, 
    email: {
         type: String
    }, 
    hashed_password: String
     authyID: String
});

Authy creates an Authy ID by generating a unique string based upon your initially registering country code and phone number.  If you add additional devices to your Authy account, they will all be registered with this same Authy ID.  If your phone number changes, you can approve this change from one of your other devices.  If there is no other device to provide approval, you will have to go through the Authy Phone Change process which has a built in delay for security reasons.

Implementing Authy

In the next few code samples, we’ll show how you can implement Authy on a NodeJS stack with a minimum amount of code and effort.   Since Authy is a REST-based cloud API, you can easily roll your own library, but for the sake of brevity, we’ll be using the npm library put together by Seegno. Additionally, you can find an example NodeJS/Angular full-stack implementation on the AuthySE Github site.

Adding Authy to User Registration

In order to register users, you’ll need to call the Authy API new user endpoint.   You will need to extend your user data model to store the Authy ID.  See the User Model section above.  The bolded code below is the additional code needed to register Authy registration.

user = new User({username: req.body.username});
user.set('hashed_password', hashPW(req.body.password));
user.set('email', req.body.email);
user.set('authyId', null);
user.save(function (err) {
        authy.registerUser({
            countryCode: req.body.country_code,
            email: req.body.email,
            phone: req.body.phone_number
        }, function (err, regRes) {

            user.set('authyId', regRes.user.id);
            // Save the AuthyID then request an SMS
            user.save(function (err) {
                    createSession(req, res, user);
            });
        });
});

The bolded code above registers a user after saving the initial user information to a database.  The authy.registerUser call returns the unique AuthyID which you then save alongside the datastore.  Once the AuthyID is saved into the database, we then create the session for the user.

Sending a TOTP Token via SMS

The easiest way to verify the second factor is via a token sent to the registered user’s phone via SMS.   In our demo code, we’ll first request an SMS from the 2FA screen by clicking ‘SMS.’

2fa-token-request
To request the Authy OneCode, you’ll need to add the following call to your middleware:

var username = req.session.username;    
User.findOne({username: username}).exec(function (err, user) { 
        authy.requestSms({ 
            authyId: user.authyId}, {force: true},  
            function (err, smsRes) { 
                  res.status(200).json(smsRes); 
        });
});

If the user has the Authy app already installed, they will receive a push notification which will open the Authy app up to the SoftToken page.  By adding `force: true`, the app will force an SMS to be sent to the user.

Verifying a OneCode Token

Once receiving a OneCode via SMS, you’ll want to verify the token.  You’ll need to add additional code to your middleware to take the token and verify it against the Authy API.

verify-token

var username = req.session.username;
User.findOne({username: username}).exec(function (err, user) {
    authy.verifyToken({
      authyId: user.authyId, 
      token: req.body.token}, 
      function (err, tokenRes) {
        req.session.authy = tokenRes.success;
        res.status(200).json(tokenRes);
    });
});

In the above code, the verifyToken callback assigns the `tokenRes.success` to the Authy session variable. If the token response is true, the user has now authenticated with a second factor.

In our demo, once you have verified with your username and session, you will finally be forwarded to the protected URL seen below.

post-login

Protecting Routes with Two Factors

When setting up routes in Node, you’ll want to restrict access of some routes to the username/password factor while other routes you’ll want to protect with both the username/password and Authy.  In the following code sample, we will inspect the both the loggedIn and Authy booleans and forward appropriately.   This route protection is for those pages you want to protect with both login and Authy.

function requireLoginAnd2FA(req, res, next) {
    if (req.session.loggedIn && req.session.authy) {
        next();
    } else if (req.session.loggedIn && !req.session.authy) {
        res.redirect("/2fa");
    } else {
        res.redirect("/login");
    }
}

In the following function, we gate access with only the loggedIn boolean state.

function requireLogin(req, res, next) {
    if (req.session.loggedIn) {
        next();
    } else {
        res.redirect("/login");
    }
}

In the following route setup, the protected directory requires two factors for access while the 2FA directory requires only a login session.

app.all('/protected/*', requireLoginAnd2FA, function (req, res, next) {
    next();
});

app.all('/2fa/*', requireLogin, function (req, res, next) {
    next();
});

Quick and Easy 2FA with Authy

So there you have it, a super-simple example which demonstrates how to implement Authy in a Node environment.   As I mentioned above, you can clone this entire implementation and give it a try yourself on our Github site.  

If the NodeJS implementation is too much, you could clone our cURL examples and give the entire Authy API a try via the command line or Postman examples available on our site as well.

Feel free to reach out if you have any questions or alternatively just create an issue on Github.

About the author Josh Staples

Josh is a Solutions Architect for Authy at Twilio. Before joining Twilio, he worked in creative full-stack rolls for both web and 3D oriented startups.

We can text you a link to get started:

Close