Almost two years ago I had written a tutorial around 2FA in a Node.js API with time-based one-time passwords. If you’re unfamiliar, two-factor authentication is becoming the norm, which it wasn’t necessarily back in 2017. If you’re managing user accounts in your web applications, it is critical that you offer your users a second factor of authentication to prevent phishing and malicious login attempts.
While the previous tutorial is still valid, it uses a less popular library to accomplish the task. This time around we’re going to explore using a more popular library called Speakeasy to manage two-factor authentication (2FA) within our Node.js with Express.js application.
Before getting too far ahead of ourselves, I wanted to point out that time-based one-time passwords (TOTP) are not the only way to accomplish 2FA in modern web applications. In fact, hardware tokens have been picking up steam and I wrote an example in a tutorial titled, Implementing U2F Authentication with Hardware Keys Using Node.js and Vue.js. We won’t be focusing on the hardware approach, I just wanted to point out that it was possible.
There are many 2FA project’s on GitHub, but Speakeasy stands out due to how much activity it has. To make life easier, we’re going to play around with this package in a new project.
Assuming you have the Node Package Manager (NPM) available, execute the following:
npm init -y
npm install express body-parser speakeasy --save
The above commands will create a new Node.js project and install the Speakeasy package. They will also install Express.js and a necessary package for handling POST requests with a payload.
We’re going to do all of our coding in an app.js file, so make sure that you create it within your active project directory.
Open the project’s app.js file and include the following boilerplate code:
const Express = require("express");
const BodyParser = require("body-parser");
const Speakeasy = require("speakeasy");
var app = Express();
app.use(BodyParser.json());
app.use(BodyParser.urlencoded({ extended: true }));
app.post("/totp-secret", (request, response, next) => { });
app.post("/totp-generate", (request, response, next) => { });
app.post("/totp-validate", (request, response, next) => { });
app.listen(3000, () => {
console.log("Listening at :3000...");
});
In the above code you’ll see that we’ve initialized Express Framework and created three possible endpoint functions. The totp-secret
function will generate a secret token to be saved in an application like Google Authenticator. The totp-generate
function will generate a time-based one-time password (TOTP) based on the secret token, and the totp-validate
function will validate that the TOTP is valid for a given secret and is not expired. Realistically, the totp-generate
function wouldn’t exist in the web application, it would exist in the client facing application.
The Speakeasy package is very easy to use. Let’s take a look at generating secret tokens first.
app.post("/totp-secret", (request, response, next) => {
var secret = Speakeasy.generateSecret({ length: 20 });
response.send({ "secret": secret.base32 });
});
The above example is very basic. Realistically you’d store the secret token in your database and associate it to a particular user. After it is provided to the user during generation, it should never leave the backend application again. When the user needs to generate TOTP tokens, they’d do something like this:
app.post("/totp-generate", (request, response, next) => {
response.send({
"token": Speakeasy.totp({
secret: request.body.secret,
encoding: "base32"
}),
"remaining": (30 - Math.floor((new Date()).getTime() / 1000.0 % 30))
});
});
In typical scenarios, tokens only exist for 30 seconds at a time. The timer doesn’t necessarily start at 30 seconds when the token is generated, but instead is based on the unix timestamp. The expiration times can be extended through the validation process.
Finally we have the validation function:
app.post("/totp-validate", (request, response, next) => {
response.send({
"valid": Speakeasy.totp.verify({
secret: request.body.secret,
encoding: "base32",
token: request.body.token,
window: 0
})
});
});
The validation process takes the secret token, which should come from the database directly, and the expiring token that the user provides. A window
of zero means that we’re using only the 30 second period and we’re not keeping track of codes X iterations ago. If the token is valid and not expired, true will be returned back to the client.
Be careful when adjusting the window
value as it is typically the norm to only allow one TOTP every 30 seconds. Allowing tokens to be valid 90 minutes in the future may not be a good idea.
You just saw how to add a second factor of authentication to your web applications with time-based one-time passwords. This tutorial was similar to my previous tutorial, but this time we’re using Speakeasy as the one-time password package.
Now that you have secret tokens being generated, why not implement your own application for generating TOTP tokens? Check out my previous tutorial titled, Build a Time-Based One-Time Password Manager with NativeScript, or Build a Time-Based One-Time Password Manager with Ionic Framework, which accomplish just that.
A video version of this tutorial can be found below.