Implementing Two-Factor Authentication with NodeJS and otplib

There’s probably no better time to integrate two-factor authentication into your a than today.

Two-factor authentication (often abbreviated TFA or 2FA) is a method of authenticating clients that involves ‘two factors’ when verifying a user – a password and something the user can physically access – like a fingerprint or a random SMS code (or even better, a one-time password!).

Single-factor authentication refers to the kind of login that only requires a username (or email) and password. This is the traditional method of logging in that you’re probably used to.

Before we get lost in a whole mess of new words, let’s break down some of the important vocabulary that will make it much easier to discuss upcoming concepts.

One Time Password

A one-time password is a kind of token (a code or word) that can only be used once. Regardless of how it’s implemented, such a password should only ever be valid for a single use case, then gets disregarded. It can never be used twice.

You may already have spotted the giant bottleneck in implementing a robust OTP system. How can we ensure we only ever generate a unique token?

The answer lies in the HMAC-based One-Time Password algorithm.

Hmac-based One-Time Password algorithm (HOTP)

The HOTP algorithm depends on two pieces of information in order to produce a token we can reliably use – a moving factor and a secret key.

The secret key is probably something you’ve encountered in one form or another on some corner of the internet. It’s a token that’s shared between a server and client so we can be sure the client has somehow been granted access. Since the server is responsible for generating the tokens, the first problem we have is sharing the secret key with the client.

The second problem we have is generating a random string on the server as our counter moves upwards. This counter is the mysterious-sounding ‘moving-factor’.

In pseudocode:

The ‘secret’ is a HmacSHA1 hash (published as RFC4226 by the Internet Engineering Task Force) of the actual secret string passed to the algorithm. The resulting output is about 20 bytes long. The second step is making the output short enough to be easily read by human eyes. This truncation is handled by the TOTP algorithm and can produce any string of our desired length, eg, “556 221″

Great! Now we have a nice readable token that can be given to the user. The only problem that remains is the counter. Take a second to consider it before you throw in a simple auto-incrementing counter and call it a day

The counter has to be implemented such that it never repeats itself or the same secret token will be generated twice at some point. If this happens, it’s a huge security vulnerability. To keep it short, implementing your own counter is a pain and you shouldn’t do it unless you’re sure you know what you’re doing.

The solution to a bulletproof counter lies in literally using time’ as our counter. This then gives birth to the fabled time-based one-time password (TOTP)

Time-based One Time Password (TOTP)

Now we know the specifics of how a OTP comes about, let’s break it down even further to make sure they’re no blind spots in our implementation. If you’re interested in the technical bits, The “Time-Based One-Time Password” spec was published as RFC6238 by the IETF.
The HOTP and TOTP algorithm only differ in the fact that the latter uses time as a counter. Since both server and client have access to time, there’s no need to create and manually keep track of a counter. The epoch time (when the counter begins, can be specified as a unix timestamp if time zones are going to be a problem).

Time step

Unix time is defined in seconds (which means our counter changes every second), which isn’t ideal. A better idea would be to generate the token after a significant time interval. We will call this time interval the time step.

In order to create the required interval, we’ll redefine our counter as

A larger time step means that your app (and your users) will have a longer time to validate a new token before the old one is invalidated. However, this also means a larger attack opportunity for attackers – the key is balance.

Delay window

Another important concept that needs to be introduced is the delay window. What happens if the client sends a TOTP that’s close to expiry, but due to a latency issue, it arrive at the server after expiry? Do we reject such a token and require the user to generate a new one? We could, but a better idea is to create a delay window.

A delay window makes the TOTP validation function not only check tokens that are valid for the current step but also for the last S steps. Since the counter we are using is predictable, we can also predict what tokens will be generated in the future. The delay window can thus verify tokens from both the past and the future.

Delay widow and time step are both important concepts to have at your fingertips. Since our client is going to be Google Authenticator or a similar 2FA app, we don’t have to worry about them. These are built-in. (GA’s time-step is 30 seconds).

Sharing the secret with the user

Finally, we need a way to share the secret generated on the server with our client. This should ideally be a random (secure) string. Since we want it to be very long, having the user input it manually would be a pain. Instead, we’ll leverage QR codes!

Implementing Two Factor Authentication with NodeJS

Now that all the theory is done, you should have a solid understanding of one of the most important algorithms on the web. We should get down to implementing it with express and NodeJS. We’re going to use a fairly old and well-supported library – otplib.

For the sake of keeping this tutorial short and to the point, we won’t implement actual login logic for this app. That would mean setting up a database in addition to a server, and that’s outside the scope of this article.

Here’s how the app is going to work

Setting up

Step 1: A user enables 2FA

It’s important to note that the user has to explicitly enable two-factor authentication for it to work. Recall that the secret has to be shared between the server and client.

When the user enables 2FA, we generate a unique secret key for them:

The secret should be stored in the database together with the user that owns it.

Step 2: Share the secret with the user

Note: Google Authenticator ignores the ‘algorithm’, ‘digits’, and ‘step’ options supported by otplib. Cross-check with your authenticator app in case of errors.

The second step is finding a way we can give the client access to the secret generated by the server. Since most modern phones have cameras, a QR code is a convenient way of encoding our (inconveniently long) string so that the user doesn’t have to type it out.

To do this, we’ll use the qrcode module.

Once the image path is generated, share it with the user over your API.

In order to confirm the QR code was scanned, you could ask the user to input their fresh TOTP code. This can be verified using:

Logging In

Step 1: Username and password

The first step of a 2FA flow resembles everything you’re probably used to by now: the user first provides their username and password. The server is then responsible for verifying that the specified user exists in the database. If both username and email are correct, the API should next check if 2FA is enabled for the account.

Should 2FA be active for the account, the server should serve a simple reply such as “2FAEnabled:true”. This tells the client to send the same request again (this time to a new ‘/verify’ route) with the necessary token. The client might have to persist the credentials, but since you’re dealing with potentially sensitive data, be careful with how you store it.

If you’re uncomfortable with having a password that exposed, the server could also generate a special short-lived token that’s only valid for the ‘/verify’ route. This token (and response body) is sent to the client, who saves it.

Step 2: Verifying the TOTP

Once the client has received a response indicating 2FA has been enabled, the site or app should redirect the user to a second page or show a dialog requesting a TOTP code. Depending on how you’ve decided to implement it, the server authorizes the client based on the short-lived token or by re-sending the username and password.

The final step will look something like:

If the user provides the right TOTP code, we provide them with a normal access token that can be used to access other parts of the app!

Conclusion

Two-factor authentication adds a layer of protection to your authentication mechanism. However, it will ultimately become useless if you attempt to treat it as a solution to all your problems.

  • Both the normal login and ‘/verify’ routes should be protected from brute forcing. If someone decides to take down your 2FA system, logging in might turn out to be impossible.
  • Be careful with the delay window and time step options. You don’t want your TOTP codes to be valid for too long.

Link to the Github Repository with a project.

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

Related articles

1.07.2020

Building a Full Stack Application using RedwoodJS

This article explains how to build a full stack application using redwoodjs. we will learn how to build some of the important modules such as ...

29.06.2020

Deploying Your NodeJS Code to a Server Every Time You Push with Github Actions

One of the biggest bottlenecks new developers face is how to deploy code to a server automatically. Github Actions allows you to break this barrier ...

28.06.2020

A Complete reference guide to Redux: State of the art state management

Do you find Redux confusing? If you want to master the redux fundamentals and start using it effectively with your favorite frontend framework then ...

1 comment

Dai Software June 16, 2020 at 5:57 pm
0

Hey, very nice site. I came across this on Google, and I am stoked that I did. I will definitely be coming back here more often. Wish I could add to the conversation and bring a bit more to the table, but am just taking in as much info as I can at the moment. Thanks for sharing.

Sign in

Forgot password?

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy

Password recovery

You can also try to

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy