Implementing TOTP Two-Factor Authentication

2FA

Two-factor authentication (2FA) adds a second layer of security beyond passwords. Among the options — SMS codes, push notifications, hardware keys — TOTP (Time-Based One-Time Passwords) is the most widely supported. It works offline, doesn't depend on a phone carrier, and is standardized in RFC 6238.

How TOTP Works

  1. Setup: Your server generates a random secret (typically 20 bytes, Base32-encoded) and shares it with the user's authenticator app via a QR code.
  2. Code generation: The authenticator app takes the current Unix timestamp, divides by a period (usually 30 seconds), HMACs it with the shared secret, and extracts a 6-digit code.
  3. Verification: When the user submits the code, your server performs the same calculation and compares.

Because both sides use the same secret and the same clock, they produce the same code independently — no network connection required.

Step 1: Generate a TOTP Secret

Use the Auth Toolkit API to create a new TOTP secret with a provisioning URI and optional QR code:

curl -X POST https://auth.toolkitapi.io/v1/auth/totp-generate \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "issuer": "MyApp",
    "account_name": "[email protected]",
    "digits": 6,
    "period": 30
  }'

The response includes everything you need:

{
  "secret": "JBSWY3DPEHPK3PXP",
  "uri": "otpauth://totp/MyApp:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=MyApp",
  "qr_code": "data:image/png;base64,...",
  "current_code": "482901"
}
  • secret: Store this securely in your database, associated with the user.
  • uri: The otpauth:// URI that authenticator apps understand.
  • qr_code: A Base64-encoded PNG image. Display it to the user so they can scan it with Google Authenticator, Authy, or 1Password.
  • current_code: The code valid right now. Use this for "enter the code to confirm setup" screens.

Step 2: Display the QR Code

Render the qr_code value directly in an <img> tag:

<img src="data:image/png;base64,..." alt="Scan with your authenticator app" />

Also display the secret as text so users can enter it manually if they can't scan. Show it in groups of four for readability: JBSW Y3DP EHPK 3PXP.

Step 3: Verify the Code

When the user enters a code, verify it against their stored secret:

curl -X POST https://auth.toolkitapi.io/v1/auth/totp-verify \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "secret": "JBSWY3DPEHPK3PXP",
    "code": "482901",
    "window": 1
  }'
{
  "valid": true,
  "drift": 0
}

The window parameter allows a ±1 time-step tolerance (i.e., the previous and next 30-second codes are also accepted). This handles minor clock drift between the user's device and your server. A drift of 0 means the code matched the current time step exactly.

Security Considerations

Store secrets encrypted

The TOTP secret is equivalent to a password. If your database is breached and secrets are stored in plaintext, an attacker can generate valid codes. Encrypt them at rest using AES-256-GCM — the Auth Toolkit's Encrypt endpoint can help.

Enforce setup confirmation

Don't enable 2FA until the user successfully enters a code. This confirms their authenticator app has the correct secret. Otherwise, they could lock themselves out.

Generate recovery codes

Provide 8–10 single-use recovery codes at setup time. Store them hashed (like passwords). If a user loses their phone, they can use a recovery code to regain access and reset 2FA.

Rate-limit verification attempts

TOTP codes have only 6 digits (1 million possibilities). Without rate limiting, an attacker could brute-force a valid code within a 30-second window. Limit to 5 attempts per minute and lock the account after 10 consecutive failures.

Don't extend the window too far

A window of 1 (accept ±1 time step) is the standard balance between usability and security. Setting it higher (e.g., 5) makes brute-force attacks easier and undermines the time-based security model.

Implementation Checklist

  • [ ] Generate a unique secret per user at 2FA enrollment
  • [ ] Display the QR code and manual secret entry option
  • [ ] Require code confirmation before activating 2FA
  • [ ] Store the secret encrypted in your database
  • [ ] Generate and display recovery codes
  • [ ] Rate-limit the verification endpoint
  • [ ] Accept current and ±1 time steps (window: 1)
  • [ ] Log 2FA events (setup, verification, recovery code use)

Try it out

Browse Tools →

More from the Blog