ScreenshotNeo

BlogHow-to

MFA Automation: TOTP Code Generation

Learn how TOTP codes work, generate them in Python, and automate an authorized MFA flow while protecting secrets and handling clock drift.

By the ScreenshotNeo team29 September 202611 min read

MFA Automation: TOTP Code Generation

TOTP automation generates the same time-based one-time password (OTP) that an authenticator would display, then submits it as part of an authorized multi-factor authentication flow. The automation must have access to the same secret as the verifier and use compatible time-step settings. RFC 6238 specifies a default time step of 30 seconds. Treat that shared secret as credential material: keep it out of source control and logs, restrict access, and use the verifier’s intended authentication channel.

This guide shows a Python implementation using the standard TOTP construction, explains how to connect code generation to a login flow, and covers verifier responsibilities, failure handling, and security limits. The examples illustrate the algorithm; they are not claims that a particular library or service was tested. Standards references: RFC 6238, RFC 4226, and NIST SP 800-63B-4 authenticator guidance.

1. What TOTP automation does

TOTP is a time-based variant of HOTP. HOTP uses a shared secret and counter; TOTP derives a counter-like moving factor from current Unix time divided by a configured time step. The prover and verifier calculate a code from that factor and the shared secret. If both sides use compatible inputs, they can arrive at the same OTP without the verifier sending the code to the authenticator.

TOTP works when the prover and verifier share the secret, time basis, and step configuration.
TOTP works when the prover and verifier share the secret, time basis, and step configuration.

The default time step in RFC 6238 is 30 seconds. A generated code is therefore associated with a time interval, but “valid for 30 seconds” is an oversimplification: a verifier may consider a bounded set of adjacent intervals to account for drift and entry delay. The verifier chooses that acceptance policy. Widening the window can help with timing errors, but also increases the time during which a code may be accepted.

Automation is appropriate only when the account owner or system owner has authorized it and has provisioned the automation with the account’s TOTP secret. It does not bypass MFA. It performs the same proof-of-secret step as a software authenticator, but stores and uses the seed in a programmatic workflow.

2. Generate a TOTP code in Python

The following runnable example uses Python’s standard library to implement the RFC 6238 construction with HMAC-SHA-1, a 30-second step, and a six-digit output. RFC 6238 also describes SHA-256 and SHA-512 variants; the algorithm must match the verifier’s configuration. Do not assume that every service uses the defaults.

import base64
import hashlib
import hmac
import os
import struct
import time


def generate_totp(secret_b32: str, *, period: int = 30, digits: int = 6,
                  digest=hashlib.sha1, timestamp: int | None = None) -> str:
    """Generate a TOTP value. secret_b32 is the Base32-encoded shared seed."""
    if period <= 0:
        raise ValueError("period must be positive")
    if not 1 <= digits <= 10:
        raise ValueError("digits must be between 1 and 10")

    # Permit the padding commonly omitted from Base32 strings.
    normalized = secret_b32.strip().replace(" ", "").upper()
    normalized += "=" * ((8 - len(normalized) % 8) % 8)
    key = base64.b32decode(normalized, casefold=True)

    now = int(time.time()) if timestamp is None else int(timestamp)
    counter = now // period
    message = struct.pack(">Q", counter)
    mac = hmac.new(key, message, digest).digest()
    offset = mac[-1] & 0x0F
    binary = struct.unpack(">I", mac[offset:offset + 4])[0] & 0x7FFFFFFF
    return str(binary % (10 ** digits)).zfill(digits)


# Set TOTP_SECRET_B32 in the process environment through your secret manager.
secret = os.environ["TOTP_SECRET_B32"]
print(generate_totp(secret))

Run it with the secret supplied through your deployment’s secret injection mechanism, not a literal committed into the script. For a local shell session, an environment variable is a convenient interface, but environment variables are not automatically a secure vault: access, process inspection, crash dumps, and deployment logs still matter. Avoid printing the secret or generated code in normal application logs.

Inputs you must match

Input What to verify Typical failure
Secret The exact seed provisioned for this account, decoded using its specified encoding Wrong account seed, truncated value, or mishandled Base32 padding
Time basis Unix time in seconds, synchronized closely enough with the verifier Host clock drift or accidental milliseconds passed as seconds
Period The same time-step setting on prover and verifier; RFC default is 30 seconds Using a non-default period against a default verifier
HMAC digest The configured digest, such as SHA-1, SHA-256, or SHA-512 Digest mismatch
Digits The output length expected by the verifier, commonly six or eight Incorrect output length or leading zero removed

The function accepts an optional timestamp to make the time input explicit, which can help when integrating with a controlled clock source. Do not use a timestamp from an untrusted client as a way to override verifier policy. Keep the output as a string: converting it to an integer removes leading zeroes, which are part of the OTP.

3. Put generation into an authorized login flow

A typical automated flow has distinct stages. Keep the secret-bearing code path narrow, and make sure the system collecting the OTP is the real service over its expected protected channel.

  1. Provision: obtain the TOTP seed through the account’s authorized enrollment process. Confirm that it is intended for this automation identity and record the required period, digest, and digit count if the service specifies them.
  2. Store: put the seed in an access-controlled secrets store or equivalent protected configuration. Limit retrieval to the component that needs to generate codes, and have a rotation or revocation path.
  3. Authenticate: complete the primary login step, then generate the OTP close to the time it will be submitted. Submit it only to the intended verifier through its documented flow.
  4. Handle response: treat success, rejection, expiry, and lockout as separate outcomes. Do not respond to rejection by logging the seed, repeatedly retrying the same value, or widening the verifier’s accepted time window without analysis.
  5. Audit carefully: log operational events such as a failed step or secret retrieval failure without recording the OTP or seed. Protect authentication logs as sensitive data.

Browser automation can be one way to interact with an authorized web login, but the OTP itself is not tied to the page or browser session. A login that asks a person to type an OTP is not thereby made resistant to phishing. A malicious intermediary can relay a manually entered code to the genuine verifier.

4. Equivalent code and command-line options

For a production application, use a maintained TOTP implementation where appropriate and verify that its options match the account configuration. The underlying inputs remain the same. Below are compact examples of how a program might invoke the Python function or provide the value to an authorized integration. Never put a real seed or OTP in shell history, command arguments visible to other users, or diagnostic output.

Python call with configuration

import os
from totp import generate_totp

code = generate_totp(
    os.environ["TOTP_SECRET_B32"],
    period=30,
    digits=6,
)
# Pass code to the authorized verifier through the application's login flow.
# Do not print it in production logs.

cURL: submit a code to your own documented endpoint

There is no universal MFA submission URL or request schema. Use the endpoint and field names documented by the service you own or are authorized to automate. This template posts a generated value from a protected local source; replace the URL and payload shape with the service’s documented contract. Do not use it against a third-party login without authorization.

curl --fail-with-body --request POST \
  --header "Content-Type: application/json" \
  --data '{"otp":"REPLACE_WITH_GENERATED_CODE"}' \
  "https://your-service.example/auth/mfa/verify"

Node.js: generate using built-in crypto

import { createHmac } from 'node:crypto';

function generateTotp(secret, { period = 30, digits = 6, timestamp = Date.now() / 1000 } = {}) {
  const counter = Math.floor(timestamp / period);
  const msg = Buffer.alloc(8);
  msg.writeBigUInt64BE(BigInt(counter));
  const mac = createHmac('sha1', secret).update(msg).digest();
  const offset = mac[mac.length - 1] & 0x0f;
  const binary = mac.readUInt32BE(offset) & 0x7fffffff;
  return String(binary % (10 ** digits)).padStart(digits, '0');
}

// This example expects secret as the decoded key bytes. Decode the account's
// Base32 seed correctly before calling it; do not treat Base32 text as raw key bytes.
const secret = Buffer.from(process.env.TOTP_SECRET_HEX ?? '', 'hex');
if (!secret.length) throw new Error('Provide the decoded key via secret injection');
const code = generateTotp(secret);
// Submit code only using the authorized service's documented MFA flow.

The Node.js function expects decoded key bytes, and its environment variable name explicitly indicates hex encoding. If your provisioned seed is Base32, decode it correctly before use; using the visible Base32 characters as raw key bytes produces a different HMAC key. The Python example performs Base32 decoding directly.

5. Verifier responsibilities: drift, replay, and rate limits

Generating a code is only one side of authentication. The verifier decides which time counters it accepts and must protect its copy of the shared secret. NIST guidance says the validity lifetime should account for expected clock drift, network delay, and the time the claimant needs to enter the OTP. Keep that window as narrow as the use case permits and monitor clock synchronization on both systems.

A manually entered OTP can be relayed, so TOTP is not phishing-resistant.
A manually entered OTP can be relayed, so TOTP is not phishing-resistant.

The verifier should accept a given OTP only once while it is valid, so an intercepted code cannot simply be submitted again during its acceptance window. It should also rate-limit failed authentication attempts. NIST SP 800-63B-4 specifically calls for effective rate limiting when the OTP output is less than 64 bits. These are verifier-side controls; a client generator cannot enforce them.

Protect the duplicated seed strongly and collect codes over an authenticated, protected channel. The same protection expectation applies to automation storage and transport. RFC 6238 says each prover should have a unique key and that keys should be randomly generated or derived with key-derivation algorithms and protected from unauthorized access and use.

6. Troubleshooting common failures

Symptom Likely cause What to check
Code rejected immediately Wrong seed, digest, period, digit count, or account Compare enrollment configuration and ensure the secret belongs to this account.
Code works inconsistently near rollover Submission crosses a time-step boundary Generate immediately before submission; avoid queuing the code for later.
All codes are offset from the authenticator Clock drift or milliseconds/seconds confusion Check system time synchronization and confirm the code uses Unix seconds.
Base32 decode error Invalid characters, whitespace, or padding mishandling Normalize only allowed formatting and verify the copied seed is complete.
Occasional rejection under load Network delay, slow login steps, or queueing Generate at the last responsible moment and inspect latency between generation and verification.
Code rejected after an earlier successful attempt Replay prevention or a reused code in the same time interval Generate a fresh code in a later interval; do not retry a code the verifier may have consumed.
Account temporarily blocked Rate limiting after failed attempts Stop automated retries, follow the service recovery process, and fix the underlying configuration.
Works in one environment but not another Different clocks, secret versions, configuration, or encoding Compare non-secret configuration and clock health; never dump the seed to diagnose.

Do not “fix” drift by permanently broadening the acceptance window without considering the security tradeoff. NIST frames the verifier’s validity lifetime around expected drift, network delay, and entry time. A broader window can accommodate more delay, while also allowing a code to remain acceptable longer.

7. Reliability, performance, and cost

TOTP generation is a small local calculation involving a time counter, HMAC, dynamic truncation, and formatting. The surrounding login flow usually determines latency and reliability: secret retrieval, primary authentication, network requests, browser loading, and service-side throttling can all take longer than the computation. Generate close to submission and avoid holding an OTP in a queue or persisting it for reuse.

Reliability depends on keeping the prover and verifier aligned on secret, clock, step, digest, and output length. Build explicit handling for clock health, unavailable secret storage, rejected codes, and rate limits. Do not automatically retry indefinitely: repeated failures can trigger verifier throttling and make diagnosis harder. A short-lived value should be treated as sensitive even though it expires.

Cost depends on the environment and verifier, not on the TOTP formula itself. Account for whatever infrastructure runs the automation, protected secret storage, and any service charges associated with the login workflow. The standards cited here do not establish vendor pricing or operational benchmarks.

8. Or skip the browser setup

For workflows whose goal is to capture a page after an authorized process has made it available, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It takes one GET request with a URL and returns a PNG, JPEG, WebP, or PDF. It does not generate or submit TOTP codes; keep MFA handling in your authorized authentication flow, then capture the resulting page as appropriate.

Example request to capture a public page:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for request options. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use the screenshot tools. The Free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000.

Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.

9. Security checklist

  • Use a unique, properly provisioned secret for the automation identity.
  • Store the secret in an access-controlled secret store; keep it out of source, logs, and error reports.
  • Match the verifier’s time-step, digest, and digit settings.
  • Keep clocks synchronized and generate the OTP close to use.
  • Send the value only to the intended verifier over its protected, authenticated channel.
  • Enforce single use, an appropriate validity window, and failed-attempt rate limits on the verifier.
  • Provide a way to revoke or rotate a seed if the automation environment is compromised.
  • Do not describe manual OTP entry as phishing-resistant.

10. FAQ

How do I automate MFA with TOTP?

Provision the automation with the account’s seed, securely retrieve it, generate a code using the same time and settings as the verifier, and submit it through the authorized authentication flow.

How do I generate a TOTP code?

Calculate the time counter from Unix time and the configured period, compute HMAC with the shared secret, apply dynamic truncation, then format the result to the configured number of digits. RFC 6238 defines the algorithm and a 30-second default period.

Why is my authenticator code not working?

Check the seed, system clock, time-step setting, digest, digit count, and whether the code was delayed or already consumed. A correct secret alone does not guarantee matching output if the other inputs differ.

Is TOTP phishing-resistant?

No. NIST says OTP authentication is not phishing-resistant: a manually entered value is not bound to the site or session and can be relayed to the genuine verifier.

Do I need a hardware token to generate TOTP?

No physical device is established as necessary for code-generation automation. TOTP can be generated in software when the authorized prover has the right shared secret and compatible settings; secure protection of that secret remains essential.

Can the verifier accept a code from the previous time step?

Verifier policy determines the accepted time window. It may allow limited drift or entry delay, but that decision should reflect the threat model and should not be treated as a client-side setting.

Primary references