This episode dissects the anatomy of a TOTP code: the Base32 shared secret, the time counter of the epoch divided by 30 seconds, HMAC-SHA1, and truncation into 6 digits. You will also learn the end-to-end architecture from generating the secret, the QR code, the first verification, up to a two-step login, including the importance of clock synchronization.

Before writing code, you must understand what's actually happening behind the 6 digits that change every 30 seconds. Episode 2 dissects TOTP from the inside: the shared secret, the time counter, HMAC-SHA1, truncation, and clock synchronization. This understanding sets you apart from developers who merely copy a library.
Once you understand the anatomy of the code, we'll walk through the main end-to-end 2FA feature architecture — from the server generating a secret, rendering a QR code, to a two-step login verification. You will also see firsthand the TOTP codes computed with otplib, and why an accurate clock is an absolute requirement for TOTP.
TOTP starts from a shared secret — a random value known only to the server and the user's device. This secret is encoded in Base32 (RFC 3548), so it only uses the letters A-Z and the digits 2-7. This Base32 form is what appears in Google Authenticator when you enter a secret manually, for example JBSWY3DPEHPK3PXP.
Base32 makes secrets easy to type by humans without easily-confused characters, and it can be encoded into an otpauth:// URI. The secret must be unique per user and never reused across accounts — episode 4 will discuss its generation in detail.
TOTP is HOTP with a counter derived from time. The counter is computed as the result of flooring the UNIX epoch (seconds since January 1, 1970) divided by the period. With a 30-second period, the counter value changes every 30 seconds — this is what makes the code appear to tick.
The simple calculation:
counter = floor(epoch_seconds / 30)Because the server and the client use real time, both produce the same counter within the same time window. This is where clock synchronization becomes crucial — TOTP fails entirely if one side is tens of seconds off.
The TOTP code is computed with the HMAC-SHA1 function, which takes the secret (after Base32 decoding) and the counter as the message. The result is 20 bytes, then dynamic truncation takes 4 bytes from the position determined by the last 4 bits of the hash, strips off 31 bits, and takes the modulus of 1,000,000. The final result is 6 digits — guaranteeing the code is always 6 numeric digits like 123456.
The full chain: the secret plus the time, then HMAC-SHA1, truncate, modulo 1,000,000, and the result is 6 digits.
RFC 6238 does not restrict TOTP to HMAC-SHA1. The algorithm parameter can be switched to SHA256 or SHA512, and period can be changed from the 30-second default. otplib reads these parameters from the provisioning URI so the code always matches what the authenticator app is configured with.
Best compatibility still lies with HMAC-SHA1, 6 digits, and a 30-second period — every app supports this combination. Treat algorithm variations as an advanced feature for special needs, not the default choice.
The implementation above has been tested thousands of times inside otplib. You only need to use the authenticator API:
node -e "
const { authenticator } = require('otplib');
const secret = 'JBSWY3DPEHPK3PXP';
const token = authenticator.generate(secret);
console.log('Current TOTP:', token);
console.log('Counter step:', Math.floor(Date.now() / 1000 / 30));
"Run that command twice within 30 seconds to see the code change. The authenticator.generate method accepts a Base32 secret and returns 6 digits — exactly what Google Authenticator produces for the same secret. Episodes 4 and 6 will use this API for enrollment and verification.
Tip
otplib accepts global options via authenticator.options = { step: 30, window: 1 }. The algorithm, digits, and period parameters in the provisioning URI are also read automatically by authenticator.generate, so the result is always consistent with the authenticator app.
The entire series is built on a single main architecture. The sequence is:
Episodes 4 through 7 dissect each of these steps one by one.
After enrollment, login becomes a two-step flow:
pendingMfa status.This architecture prevents users from accessing protected routes before completing the 2FA challenge. The full implementation is covered in episode 8.
TOTP is highly sensitive to time. RFC 6238 recommends that the server allow one step of tolerance on each side — a total window of three steps — to accommodate device clock differences. On production servers, use NTP and don't rely on a drifting server clock:
sudo timedatectl set-ntp true
timedatectl statusThe timedatectl status output shows a "System clock synchronized: yes" line when NTP is active. Episode 6 will use this step tolerance during first verification, and episode 16 tests it with artificial clock skew.
Episode 2 dissected the core of TOTP: the Base32 secret as the shared secret, the counter from the epoch divided by 30 seconds, HMAC-SHA1 with dynamic truncation into 6 digits, and the enrollment and two-step login architecture that supports the whole series.
The key takeaways:
In the next episode, episode 3, we will build the authentication foundation: login and session — password registration with bcrypt, HttpOnly cookies and JWT, the user model structure that will store the 2FA secret, and the auth endpoints that will later be injected with the MFA challenge.