authenticator vs otplib vs speakeasy
Implementing Two-Factor Authentication (2FA) in Node.js Applications
authenticatorotplibspeakeasySimilar Packages:

Implementing Two-Factor Authentication (2FA) in Node.js Applications

The packages authenticator, otplib, and speakeasy all provide tools for generating and verifying Time-based One-Time Passwords (TOTP) and HMAC-based One-Time Passwords (HOTP), which are the core algorithms behind apps like Google Authenticator and Authy. speakeasy has historically been the most popular choice due to its comprehensive feature set, including QR code generation helpers, but it has faced periods of inactivity. otplib emerged as a modern, modular alternative with a strong focus on TypeScript support and active maintenance. authenticator offers a straightforward implementation but has seen less adoption and maintenance activity compared to the other two. Choosing the right one depends on your need for long-term maintenance, TypeScript integration, and specific feature requirements like QR code handling.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
authenticator0---8 years ago(MIT or Apache-2.0)
otplib02,289612 kB55 days agoMIT
speakeasy02,757-6611 years agoMIT

TOTP Libraries Showdown: otplib vs speakeasy vs authenticator

Implementing Two-Factor Authentication (2FA) is a standard requirement for modern web applications. While the underlying math (RFC 6238 and RFC 4226) is the same for everyone, the developer experience, maintenance status, and API design of the libraries wrapping these algorithms vary significantly. Let's dig into how otplib, speakeasy, and authenticator handle the real-world tasks of generating secrets, creating QR codes, and verifying tokens.

🛠️ Installation and Setup: Modular vs Monolithic

The way you bring these libraries into your project sets the tone for your codebase.

otplib is designed with modularity in mind. You don't have to import the whole kitchen sink if you only need TOTP. This helps with tree-shaking in bundled environments and keeps your dependencies clear.

// otplib: Import only what you need
import { authenticator } from 'otplib';
// Or for more control:
// import { totp } from 'otplib/core';

speakeasy is a monolithic package. You import the entire library, which includes TOTP, HOTP, and helper functions for QR codes and base32 encoding all in one go.

// speakeasy: Import the whole package
const speakeasy = require('speakeasy');

authenticator follows a similar monolithic approach to speakeasy, exporting a single main object that handles all operations.

// authenticator: Import the main export
const authenticator = require('authenticator');

🔑 Generating Secrets and QR Codes

A critical part of 2FA setup is generating a shared secret and presenting it to the user via a QR code. This is where the differences in "batteries-included" features become obvious.

speakeasy shines here because it includes a helper to generate the otpauth:// URL string directly, which is exactly what QR code libraries (like qrcode) need. It feels very complete out of the box.

// speakeasy: Generate secret and URL in one go
const secret = speakeasy.generateSecret({
  name: "MyApp (user@example.com)",
  issuer: "MyApp",
  length: 20
});

// secret.otpsauth_url contains the ready-to-use QR string
console.log(secret.otpauth_url);

otplib takes a more decoupled approach. It generates the secret, but you must manually construct the otpauth:// URL string yourself before passing it to a QR code generator. This adds a few lines of code but gives you full control over the URL format.

// otplib: Generate secret, then build URL manually
const secret = authenticator.generateSecret();
const otpauth = authenticator.keyuri(
  "user@example.com", 
  "MyApp", 
  secret
);

// Pass 'otpauth' string to your QR library
console.log(otpauth);

authenticator provides a method to generate the key, but like otplib, it often requires you to manually format the URI string for QR code generation, lacking the direct otpauth_url property convenience found in speakeasy.

// authenticator: Generate key and format URI
const key = authenticator.generateKey("MyApp", "user@example.com");
// You must manually construct the otpauth:// URI string here
const uri = `otpauth://totp/MyApp:user@example.com?secret=${key}&issuer=MyApp`;

âś… Verifying Tokens: The Critical Path

When a user logs in, you must verify the 6-digit code they entered. All three libraries do this, but their API signatures and default behaviors differ slightly.

otplib uses a clean, async-friendly API. It throws an error if the token is invalid (depending on configuration) or returns a boolean. It handles time-window checking (allowing for slight clock drift) elegantly.

// otplib: Verify with built-in window handling
const token = "123456";
const secret = "...user's secret...";

const isValid = authenticator.verify({
  token: token,
  secret: secret,
  window: 1 // Checks previous and next token too
});

if (isValid) {
  console.log("Login successful");
}

speakeasy returns a boolean directly. It is straightforward but requires you to ensure you are passing the correct options for time-step and window, or it might be too strict.

// speakeasy: Verify returns true/false
const tokenValid = speakeasy.totp.verify({
  secret: "...user's secret...",
  encoding: "base32",
  token: "123456",
  window: 1
});

if (tokenValid) {
  console.log("Login successful");
}

authenticator also provides a verification method, but its API can feel slightly less intuitive regarding option passing compared to the explicit object patterns of the others.

// authenticator: Verify token
const isValid = authenticator.verifyToken(
  "...user's secret...",
  "123456"
);
// Note: Check docs for specific window/drift configuration options
if (isValid) {
  console.log("Login successful");
}

📦 TypeScript and Modern Development

For modern frontend and full-stack teams, TypeScript support is not a luxury; it is a requirement.

otplib is written in TypeScript. This means you get perfect type safety, auto-completion, and interface definitions out of the box. There is no need to install separate @types packages.

// otplib: Full TypeScript support
import { authenticator } from 'otplib';

const secret: string = authenticator.generateSecret();
// Your IDE knows exactly what methods are available

speakeasy is written in older JavaScript. While community-maintained type definitions (@types/speakeasy) exist, they can sometimes lag behind or miss nuanced edge cases. You are relying on the community to keep the types accurate.

// speakeasy: Requires @types/speakeasy
import * as speakeasy from 'speakeasy';
// Types are available but not native to the package

authenticator generally lacks robust, official TypeScript definitions. Using it in a strict TypeScript project often requires writing your own declaration files or disabling strict checks, which increases technical debt.

🚨 Maintenance and Security Trust

Security libraries live or die by their maintenance schedule. If a vulnerability is found in the underlying algorithm implementation or a dependency, you need a maintainer who will patch it quickly.

  • otplib: Currently the most active. It receives regular updates, has clear issue tracking, and is built with modern standards. It is the recommended choice for new projects.
  • speakeasy: Has a long history and is widely used, but it has experienced long periods of inactivity in the past. While still functional, relying on it for new high-security applications carries a risk that future issues might not be addressed promptly.
  • authenticator: Shows the least amount of recent activity and community engagement. In the security world, a quiet repository is often a red flag. It is difficult to recommend this for production systems where accountability is key.

📊 Summary Comparison

Featureotplibspeakeasyauthenticator
ArchitectureModular, Tree-shakableMonolithicMonolithic
QR Code HelperManual URL constructionBuilt-in otpauth_urlManual URL construction
TypeScriptNative (Written in TS)Community Types (@types)Poor/None
MaintenanceActiveSporadicLow Activity
API StyleModern, Object-basedClassic, Options ObjectClassic, Function-based

đź’ˇ The Final Verdict

If you are building something today, otplib is the clear winner. Its native TypeScript support, modular design, and active maintenance make it the most professional choice. The minor inconvenience of manually constructing the QR code URL is a small price to pay for the security and DX benefits.

Use speakeasy only if you are stuck maintaining an older codebase that already uses it, or if the built-in QR URL generator is a absolute deal-breaker and you cannot add a small utility function to replicate it.

Avoid authenticator for new projects. The lack of momentum and weaker ecosystem support makes it a risky foundation for authentication features.

How to Choose: authenticator vs otplib vs speakeasy

  • authenticator:

    Avoid choosing authenticator for new professional projects. It lacks the active community momentum, extensive documentation, and modular architecture found in otplib. Unless you have a very specific, niche requirement that only this package meets, the other two options offer significantly better engineering guarantees.

  • otplib:

    Choose otplib if you are starting a new project today, especially one written in TypeScript. It is actively maintained, modular (allowing you to import only what you need), and has first-class type definitions. It is the safest bet for long-term security updates and modern JavaScript ecosystem compatibility.

  • speakeasy:

    Choose speakeasy only if you are maintaining a legacy system that already depends on it or if you specifically need its built-in helper functions for generating QR code data URIs without adding extra dependencies. Be aware that its maintenance history has been inconsistent, so evaluate the risk of future stagnation before adopting it for new critical infrastructure.

README for authenticator

Node.js Authenticator

| Sponsored by ppl

Two- and Multi- Factor Authenication (2FA / MFA) for node.js

There are a number of apps that various websites use to give you 6-digit codes to increase security when you log in:

There are many Services that Support MFA, including Google, Microsoft, Facebook, and Digital Ocean for starters.

This module uses notp which implements TOTP (RFC 6238) (the Authenticator standard), which is based on HOTP (RFC 4226) to provide codes that are exactly compatible with all other Authenticator apps and services that use them.

Browser & Commandline Authenticator

You may also be interested in

Install

node.js api

npm install authenticator --save

command line

npm install authenticator-cli --global

Usage

node.js api

'use strict';

var authenticator = require('authenticator');

var formattedKey = authenticator.generateKey();
// "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz"

var formattedToken = authenticator.generateToken(formattedKey);
// "957 124"

authenticator.verifyToken(formattedKey, formattedToken);
// { delta: 0 }

authenticator.verifyToken(formattedKey, '000 000');
// null

authenticator.generateTotpUri(formattedKey, "john.doe@email.com", "ACME Co", 'SHA1', 6, 30);
//
// otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30

command line

# see help
authenticator --help

# generate a key and display qr code
authenticator --qr

API

generateKey()                               // generates a 32-character (160-bit) base32 key

generateToken(formattedKey)                 // generates a 6-digit (20-bit) decimal time-based token

verifyToken(formattedKey, formattedToken)   // validates a time-based token within a +/- 30 second (90 seconds) window
                                            // returns `null` on failure or an object such as `{ delta: 0 }` on success

                                            // generates an `OTPAUTH://` scheme URI for QR Code generation.
generateTotpUri(formattedKey, accountName, issuer, algorithm, digits, period)

OTPAuth Scheme

Note that ISSUER is specified twice for backwards / forwards compatibility.

QR Code

See https://davidshimjs.github.io/qrcodejs/ and https://github.com/soldair/node-qrcode.

Example use with qrcode.js in the browser:

'use strict';

var el = document.querySelector('.js-qrcode-canvas');
var link = "otpauth://totp/{{NAME}}?secret={{KEY}}";
var name = "Your Service";
                                              // remove spaces, hyphens, equals, whatever
var key = "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz".replace(/\W/g, '').toLowerCase();

var qr = new QRCode(el, {
  text: link.replace(/{{NAME}}/g, name).replace(/{{KEY}}/g, key)
});

Formatting

All non-alphanumeric characters are ignored, so you could just as well use hyphens or periods or whatever suites your use case.

These are just as valid:

  • "acqo ua72 d3yf a4e5 - uorx ztkh j2xl 3wiz"
  • "98.24.63"

0, 1, 8, and 9 also not used (so that base32). To further avoid confusion with O, o, L, l, I, B, and g you may wish to display lowercase instead of uppercase.

TODO: should this library replace 0 with o, 1 with l (or I?), 8 with b, 9 with g, and so on?

90-second Window

The window is set to +/- 1, meaning each token is valid for a total of 90 seconds (-30 seconds, +0 seconds, and +30 seconds) to account for time drift (which should be very rare for mobile devices) and humans who are handicapped or otherwise struggle with quick fine motor skills (like my grandma).

Why not SpeakEasy?

It doesn't use native node crypto and there are open security issues which have been left unaddressed.