What is TOTP? A short guide for developers (RFC 6238 explained)

What is TOTP (Time-based One-Time Password)? A concise RFC 6238 explanation for developers with code examples (Node, Python, Go), troubleshooting tips, and a free online TOTP tool.

 min. read
August 28, 2025

TOTP (Time-based One-Time Password) is a simple, widely used method for generating short-lived numeric codes from a shared secret and the current time (RFC 6238). Typical use: 6-digit codes that refresh every 30 seconds. This guide explains how it works, common pitfalls, and shows quick examples in Node, Python, and Go. Try the live TOTP generator.

What is TOTP

TOTP stands for Time-based One-Time Password. It uses a shared secret (usually a Base32 string) and the current time to create short numeric codes that expire quickly — commonly every 30 seconds. TOTP is standardized by IETF RFC 6238 and is the mechanism behind most authenticator apps (Google Authenticator, Authy, Authgear TOTP Generator, etc.).

How TOTP works

  1. Shared secret — The server and client (authenticator) agree on a shared secret when 2FA is set up. That secret is often encoded in Base32.
  2. Time step — The current Unix time is divided by a timestep (typically 30 seconds) to create a moving counter.
  3. HMAC — The server and client compute an HMAC over the counter using a chosen hash algorithm (SHA-1, SHA-256, or SHA-512).
  4. Truncation — A dynamic truncation step extracts a numeric code of fixed length (commonly 6 digits).
  5. Validation — On login, the server computes the expected TOTP(s) and checks whether the user-provided code matches (often allowing ±1 timestep for clock skew).

In short:

TOTP = Truncate(HMAC(secret, floor(currentTime / timestep))) % 10^digits.

Important parameters

  • Secret format: Base32 (alphanumeric, e.g., JBSWY3DPEHPK3PXP).
  • Timestep (period): Usually 30 seconds (RFC 6238 recommended).
  • Digits: Usually 6, sometimes 8. 6 balances usability & security.
  • Algorithm: SHA-1 (most compatible), SHA-256 or SHA-512 (stronger hashes if supported by both ends).

Quick code examples

Replace SECRET_BASE32 with your Base32 secret. These examples use standard, well-maintained libraries.

Node (otplib)

// npm i otplib
const { totp } = require('otplib');

totp.options = { algorithm: 'sha1', digits: 6, step: 30 };
const secret = 'SECRET_BASE32';
const code = totp.generate(secret);
console.log('TOTP:', code);

// Verify:
const valid = totp.check(code, secret);
console.log('valid:', valid);

Python (pyotp)

# pip install pyotp
import pyotp

secret = "SECRET_BASE32"
totp = pyotp.TOTP(secret)  # defaults: digits=6, interval=30, SHA1
print("TOTP:", totp.now())

# verify
print("valid:", totp.verify(totp.now()))

Go (pquerna/otp)

import (
  "fmt"
  "time"
  "github.com/pquerna/otp/totp"
)

func main() {
  secret := "SECRET_BASE32"
  code, _ := totp.GenerateCode(secret, time.Now())
  fmt.Println("TOTP:", code)

  valid := totp.Validate(code, secret)
  fmt.Println("valid:", valid)
}

Common pitfalls & troubleshooting

  • Clock skew — TOTP depends on accurate time. If codes “don’t match”, sync the server and authenticator clock (NTP) or allow a verification window of ±1 timestep.
  • Wrong secret format — Ensure the secret is Base32 and remove spaces. If you have a QR otpauth:// URL, extract secret=.
  • Algorithm/digit mismatch — Server and client must use the same algorithm (SHA-1/256/512) and digits (6/8). Mismatches are a very common source of failures.
  • Using production secrets in shared tools — Don’t store production secrets in online tools. Use local/offline generators or internal test secrets.

Security considerations

  • TOTP is something you have (the shared secret) — it’s effective against remote password-only attacks but can be bypassed if the secret is stolen.
  • Use TOTP as part of a multi-factor auth approach, combine with secure server-side policies (rate limiting, anomaly detection).
  • Prefer SHA-256/512 if you control both sides and want a stronger HMAC than SHA-1 — but keep compatibility in mind.

When to use TOTP

  • Good: human logins, admin access, developer test flows, internal tools.
  • Not great: high-risk unattended API access (use client certificates, OAuth tokens, or hardware-backed keys for stronger guarantees).
  • Alternative MFA methods: push-based MFA, FIDO2/WebAuthn i.e. Passkeys (phishable-resistant), hardware tokens.

How to test your integration

  1. Extract the shared secret from the provisioning flow (Base32).
  2. Generate a TOTP locally using one of the library examples above.
  3. Verify server-side acceptance with a small window (±1 step) for clock skew.
  4. Test algorithm/digits mismatch scenarios intentionally to confirm your server logs clear errors.
  5. Use a test-only tool to preview codes without exposing production secrets — try: Authgear TOTP Authenticator: https://www.authgear.com/tools/totp-authenticator

FAQ

Q: What is the standard TOTP timestep?
A: 30 seconds (RFC 6238 recommends 30s).

Q: How many digits should I use?
A: 6 digits is standard; 8 digits adds entropy but reduces usability.

Q: Is TOTP secure?
A: TOTP is secure against many attacks when secrets are kept safe and used with additional controls (rate limits, device binding). For highest security, consider FIDO2/Passkeys where appropriate.

Preferences

Privacy is important to us, so you have the option of disabling certain types of storage that may not be necessary for the basic functioning of the website. Blocking categories may impact your experience on the website.

Accept all cookies

These items are required to enable basic website functionality.

Always active

These items are used to deliver advertising that is more relevant to you and your interests.

These items allow the website to remember choices you make (such as your user name, language, or the region you are in) and provide enhanced, more personal features.

These items help the website operator understand how its website performs, how visitors interact with the site, and whether there may be technical issues.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.