DEV Community

Tapan Rai
Tapan Rai

Posted on

Learn by building: What is TOTP? Building a Simple TOTP Authentication Server with Node.js

TOTP stands for time-based one-time passcode. This code is meant to grant users one-time access to an application. TOTPs have become a staple in enhancing security through two-factor authentication (2FA).

Implementing a TOTP authentication server can significantly increase the security of your application by requiring users to provide not only something they know (their password) but also something they have (a one-time code generated by their device).


How does TOTP Work?

TOTP consists of two key components to generate the OTP codes.

  1. A seed. This is a static secret key that is shared between the client and server.

  2. A moving factor. This is a component that changes every time a new OTP is requested or at set periods of time. The moving factor for TOTP is Unix time.

The algorithm uses a form of symmetric key cryptography since the same key is used by both the client and the server to independently generate the OTP.

Here are the stages of TOTP authentication:

Stage 1 - Initialization

  • When setting up TOTP for the first time, the server generates a shared seed for each user. This key is securely transferred to the user's TOTP generator, usually by encoding it into a QR code that the user scans with a TOTP app.

TOTP Generation Flow

Stage 2 - TOTP Generation

  • The TOTP generator (the user's app) takes the current time and divides it by the time step to calculate the number of time steps since a reference time (usually Unix epoch time, January 1, 1970).
  • It then uses an algorithm, typically HMAC-SHA-1, combining the seed and the time step count as inputs to generate a hash value.
  • A portion of this hash value is then extracted and converted into a 6 to 8 digit code, which is the TOTP.

Stage 3 - Authentication

  • The user enters the TOTP displayed by their generator app into the online service within its validity period.
  • Simultaneously, the server, which knows the shared secret and the current time, generates the expected TOTP using the same process.
  • The server then compares the TOTP entered by the user with the expected TOTP it has generated.

Stage 4 - Verification

  • If the TOTPs match, the authentication is successful, and the user is granted access.
  • If they do not match, access is denied, which could be due to entering an expired TOTP or a synchronization issue between the server and the user's device.

Now, let's build our own TOTP server


Building a simple TOTP server

Prerequisites

  • Basic knowledge of JavaScript and Node.js
  • Node.js installed on your development machine
  • An understanding of REST APIs and how to interact with them

Step 1: Setting Up Your Project

First, create a new directory for your project and initialize a new Node.js application by running:



mkdir totp-auth-server
cd totp-auth-server
npm init -y


Enter fullscreen mode Exit fullscreen mode

Install the necessary dependencies. You'll need express for creating the server and otplib for generating and verifying TOTP tokens.



npm install express otplib qrcode


Enter fullscreen mode Exit fullscreen mode

Step 2: Creating the Server

Create an index.js file in your project root. This file will be the entry point of your application. Start by setting up an Express server:



const express = require('express');
const app = express();

app.use(express.json()); // Enables express to parse JSON bodies

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server is listening on port ${PORT}`));


Enter fullscreen mode Exit fullscreen mode

Step 3: Generate a TOTP Secret and QR Code

Incorporate otplib and qrcode to generate a TOTP secret for each user and a QR code for easy setup in a TOTP application like Google Authenticator or Authy.



const { authenticator } = require('otplib');
const QRCode = require('qrcode');

// Temporary in-memory storage for demonstration
let userSecrets = {};

app.post('/user/register', async (req, res) => {
  const { email } = req.body;
  if (!email) {
    return res.status(400).json({ message: 'Email is required' });
  }

  const secret = authenticator.generateSecret();
  // Store the secret using the email as a key
  userSecrets[email] = secret;

  const otpauth = authenticator.keyuri(email, 'YourServiceName', secret);

  try {
    const qrCodeUrl = await QRCode.toDataURL(otpauth);
    res.json({ email, qrCodeUrl, secret: process.env.NODE_ENV === 'development' ? secret : undefined });
  } catch (error) {
    res.status(500).json({ message: 'Error generating QR code', error });
  }
});


Enter fullscreen mode Exit fullscreen mode

This endpoint generates a unique TOTP secret for a user and returns it along with a QR code image URL. The secret is what otplib uses to generate and verify tokens, while the QR code facilitates easy addition of the account to a TOTP application.

Step 4: Verifying TOTP Tokens

Create an endpoint to verify the token provided by the user. This step is crucial for logging in or performing sensitive operations within your application.



app.post('/user/verify', (req, res) => {
  const { email, token } = req.body;

  if (!email || !token) {
    return res.status(400).json({ message: 'Email and token are required' });
  }

  // Retrieve the secret using the email as a key
  const secret = userSecrets[email];

  if (!secret) {
    return res.status(404).json({ message: 'User not found' });
  }

  try {
    const isValid = authenticator.verify({ token, secret });

    if (isValid) {
      res.json({ verified: true });
    } else {
      res.status(400).json({ verified: false, message: 'Invalid token' });
    }
  } catch (error) {
    res.status(500).json({ message: 'Verification failed', error });
  }
});


Enter fullscreen mode Exit fullscreen mode

You now have a basic TOTP authentication server that can generate secrets, produce QR codes for easy setup, and verify tokens.

Word of caution ⚠️

While building your own TOTP server can be an educational experience, for most businesses and applications, leveraging existing, proven solutions is advisable. These solutions offer a balance of security, reliability, and cost-effectiveness, allowing you to focus on your core product or service. If you need customization, many existing solutions offer extensive APIs and configuration options that can meet your needs without the risks and overhead of a homegrown system.

Top comments (0)