totp

TOTP directive for Time-based One-Time Password authentication

totp

Validates Time-based One-Time Password (TOTP) codes per RFC 6238. TOTP generates time-synchronized one-time codes using HMAC-SHA1 and a shared secret. The authentication compares the submitted code against codes generated for the current time window and configurable past/future windows to account for clock drift.

Context

Valid inside authentication blocks. Can be combined with other authentication methods like pap for two-factor authentication.

Basic Syntax

authentication {
    backend {
        name "USERS";
        query "FIND_USER";
    }

    # Validate TOTP code in password field
    totp {
        resync_window 1 0;
        secret_type "hex";
    }
}

Two-Factor Authentication Example

authentication {
    backend {
        name "USERS";
        query "FIND_USER";
    }

    # Validate password (all but last 6 characters)
    pap {
        range -6 0 exclusive;
    }

    # Validate TOTP code (last 6 characters)
    totp {
        range -6 0;
        resync_window 1 0;
    }
}

Parameters

resync_window

Defines the timestep tolerance for clock drift. Accepts one or two parameters:

  • Single parameter: resync_window 1; creates a symmetric window checking 1 timestep before and 1 timestep after current time
  • Two parameters: resync_window 2 1; checks 2 timesteps before and 1 timestep after current time

Default: 1 0 (checks 1 timestep in the past, none in the future)

With the default 30-second timestep, resync_window 1 0 accepts codes from the current 30-second window and the previous 30-second window (allowing up to 60 seconds of clock drift into the past).

Values must be non-negative integers (0-65535).

secret_type

Specifies the encoding format of the shared secret retrieved from the backend:

  • "hex" - Hexadecimal encoding (default)
  • "base32" - Base32 encoding (RFC 4648)
  • "auto" - Automatically detects hex or base32

Default: "hex"

The secret must be set via hmac-otp.secret in the backend mapping.

min_secret_bits

Enforces a minimum secret length in bits. If the decoded secret is shorter than this value, authentication is rejected with a warning logged and counted.

Syntax: min_secret_bits <bits>;

  • Default: 128 (as required by RFC 4226)
  • Valid range: 0-65535
  • Common values: 128 (16 bytes), 160 (20 bytes, recommended by RFC 4226)

Example: min_secret_bits 160; requires at least a 20-byte (160-bit) secret.

RFC 4226 states: "The length of the shared secret MUST be at least 128 bits. This document RECOMMENDs a shared secret length of 160 bits."

To disable the minimum check (not recommended), use min_secret_bits 0;.

range

Extracts a substring from the password field for TOTP validation. Useful for two-factor authentication where password and TOTP code are concatenated.

Syntax: range <start> <end> [exclusive]

  • Negative indices count from the end (-6 means 6 characters from the end)
  • exclusive keyword excludes the specified range (extracts everything except the range)
  • Indices are 0-based

Examples:

# Extract last 6 characters
range -6 0;

# Extract first 6 characters
range 0 6;

# Extract all but last 6 characters (for password in 2FA)
range -6 0 exclusive;

Backend Mapping

The backend must populate TOTP-related context variables:

backends {
    sqlite "USERS" {
        filename "users.db";
        query "FIND_USER" {
            statement "SELECT username, password, totp_secret, totp_digits, totp_timestep FROM users WHERE username = ?";
            bindings {
                aaa.identity;
            }
            mapping {
                user.username = username;
                user.password = password;
                hmac-otp.secret = totp_secret;       # Required
                hmac-otp.digits = totp_digits;       # Optional (default: 6)
                hmac-otp.timestep = totp_timestep;   # Optional (default: 30)
            }
        }
    }
}

Required context variables:

  • hmac-otp.secret - Shared secret (hex or base32 encoded)

Optional context variables:

  • hmac-otp.digits - Number of digits in TOTP code (default: 6, typically 6-8)
  • hmac-otp.timestep - Time window in seconds (default: 30)
  • hmac-otp.timestep.origin - Unix timestamp origin (default: 0, rarely changed)

Replay Attack Protection

To prevent replay attacks, track the last used timestep:

# In backend
mapping {
    hmac-otp.secret = totp_secret;
    hmac-otp.timestep.last = totp_last_timestep;  # Load last used timestep
}

# In policy
caches {
    cache "last_timestep" {
        timeout 1800s;
    }
}

authentication {
    # ... backend query ...

    # Load last timestep from cache
    if any {
        cache.last_timestep[aaa.identity] != none;
    } then {
        modify {
            hmac-otp.timestep.last = cache.last_timestep[aaa.identity][0];
        }
    }

    totp {
        resync_window 1 0;
    }

    # Save current timestep after successful auth
    modify {
        cache.last_timestep[aaa.identity] = hmac-otp.timestep.last;
    }
}

See Also

Navigation