Radiator Server Documentation — v10.33.2

totp

TOTP action for Time-based One-Time Password authentication

Table of Contents
  • totp
  • Context
  • Basic Syntax
  • Two-Factor Authentication Example
  • Parameters
  • resync_window
  • secret_type
  • min_secret_bits
  • range
  • Result
  • Backend Mapping
  • Replay Attack Protection
  • See Also

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 @execute blocks. Can be combined with other authentication methods like pap for two-factor authentication.

Basic Syntax

@execute {
    backend {
        name "USERS";
        query "FIND_USER";
    }

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

Two-Factor Authentication Example

@execute {
    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;

Result

The totp action produces the following pipeline results:

  • Accept: The submitted TOTP code matches a valid code within the configured time window.
  • Reject: Authentication failed. This occurs when:
    • The shared secret is shorter than min_secret_bits requires.
    • No authentication response is present in the request.
    • The authentication response is too short for the configured range.
    • The OTP digit count does not match the expected number of digits.
    • The OTP contains non-numeric characters.
    • The OTP code does not match any valid code in the time window.
    • The OTP code was already used (replay protection, when hmac-otp.timestep.last is set).

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;
    }
}

@execute {
    # ... 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
  • accept

  • all

  • any

  • append

  • assert

  • backend

  • challenge

  • chap

  • conditions

  • copy

  • count

  • debug

  • discard

  • each

  • eap

  • error

  • filter

  • first

  • hotp

  • http-basic-auth

  • if

  • ignore

  • invoke

  • log

  • map

  • message

  • modify

  • mschap

  • mschapv2

  • none

  • pap

  • reason

  • reject

  • reject_errors

  • replace

  • reply

  • rewrite

  • set

  • sleep

  • sometimes

  • stop

  • totp

  • trace

  • try

  • until

  • while

  • with

  • yubikey

Related
  • hotp
  • pap
  • chap
  • mschapv2