Lue dans les fichiers PHP du paquet au commit 069bf3092f64 : ses espaces de noms, classes, interfaces et énumérations, leurs constantes, propriétés et méthodes publiques avec leur PHPDoc, et les options que prennent ses fabriques. Rien n’en est exécuté.
CacheUsedStepsclassThe last time step each account used, in any PSR-6 pool or PSR-16 cache: plain PHP, tests, and a host's cache pool (the same one its throttle uses is fine). Each entry lives only as long as its code could still verify.
2 methods
SealedTotpSecretsclassAn application's TotpSecrets store with its secrets sealed by a box (SodiumSecretBox: Marco's decision QA5): what the store keeps is the box's, what TotpMethod reads is the base32 secret. A secret is opened only when a code is verified or the enrolment's QR code is drawn (secret()); whether an account has one is read without opening it (has()). A secret that does not open throws (Exception\UnreadableSecret): never "no secret", which would let the account in without its second factor.
5 methods
SodiumSecretBoxclassThe TOTP secrets sealed at rest with libsodium's secretbox (XSalsa20 and Poly1305: encrypted and authenticated) under one 32-byte key - Marco's decision QA5. What the store keeps:
6 methods · 1 constant
TotpMethodclassAn authenticator application (TOTP, RFC 6238) as a second factor, over spomky-labs/otphp - the library scheb/2fa verifies with, on the parameters omnibase's User gives it (SHA1, 30 seconds, 6 digits) and the leeway every site sets (0): a code scheb accepts, this accepts, and the secrets kept today (user.secret, base32) go on working. One thing more than scheb: a code is accepted once, and none older than the last one accepted (Contract\UsedSteps).
10 methods · 4 constants
SecretBoxinterfaceThe secrets at rest (Marco's decision QA5, 2026-10-20: the TOTP secret encrypted with sodium, its key in the secrets vault). SodiumSecretBox is the box; SealedTotpSecrets wraps the application's TotpSecrets store with it, and TotpMethod never knows. Both hosts (omnibridge/symfony, omnibridge/laravel) build it from the same key.
2 methods
UsedStepsinterfaceThe time steps whose code an account already used: a code is accepted once (RFC 6238 §5.2), and none older than the last one accepted. Omniauth\Otp\CacheUsedSteps keeps them in any PSR-6 pool or PSR-16 cache; a host may keep them anywhere else (a column, its rate limiter's store).
1 method
UnreadableSecretclassA sealed secret that does not open: sealed under another key (a key rotated without its rows, a host on the wrong key), or altered. Never answered as "no secret": an account would then sign in without the second factor it set up. The message names no key and no secret.
AuthenticatorAppclassAn authenticator application in PHP, for tests: it scans the otpauth:// address an enrolment gives (or takes a secret) and shows the code of the moment on the clock given - the code a person would type.
5 methods
Omniauth\Otp
CacheUsedSteps class
final class CacheUsedSteps implements UsedSteps CacheUsedSteps.php:19
The last time step each account used, in any PSR-6 pool or PSR-16 cache: plain PHP, tests, and a host's cache pool (the same one its throttle uses is fine). Each entry lives only as long as its code could still verify.
Read then written, not atomic: two requests at the same instant with the same code may both pass. The throttle (five tries in 15 minutes per account) bounds what that leaves; a host that needs it exact keeps the steps where it can lock.
CacheUsedSteps::__construct()
public function __construct(readonly CacheItemPoolInterface|CacheInterface $cache, readonly string $prefix = 'omniauth.totp.')CacheUsedSteps::claim()
public function claim(string $handle, int $step, int $ttl): boolSealedTotpSecrets class
final class SealedTotpSecrets implements TotpSecrets SealedTotpSecrets.php:17
An application's TotpSecrets store with its secrets sealed by a box (SodiumSecretBox: Marco's decision QA5): what the store keeps is the box's, what TotpMethod reads is the base32 secret. A secret is opened only when a code is verified or the enrolment's QR code is drawn (secret()); whether an account has one is read without opening it (has()). A secret that does not open throws (Exception\UnreadableSecret): never "no secret", which would let the account in without its second factor.
SealedTotpSecrets::__construct()
public function __construct(readonly TotpSecrets $store, readonly SecretBox $box)SealedTotpSecrets::secret()
public function secret(object $account): ?stringSealedTotpSecrets::has()
public function has(object $account): boolWhether the account has a secret, without opening it.
SealedTotpSecrets::enable()
public function enable(object $account, string $secret): voidSealedTotpSecrets::disable()
public function disable(object $account): voidSodiumSecretBox class
final class SodiumSecretBox implements SecretBox SodiumSecretBox.php:27
The TOTP secrets sealed at rest with libsodium's secretbox (XSalsa20 and Poly1305: encrypted and authenticated) under one 32-byte key - Marco's decision QA5. What the store keeps:
otp:v1:<base64url, unpadded, of the 24-byte nonce then the box>
130 characters for a 52-character secret (scheb/2fa's and TotpMethod's): omnibase's user.secret (VARCHAR 255) holds it as it is. The prefix names the format and its version; a base32 secret (A-Z, 2-7, =) never starts with it, so open() tells a sealed secret from one kept in clear before the box and returns the latter as it is, until every row is sealed; seal() leaves a sealed value as it is (a rewrite of the rows runs twice harmlessly).
The key never lives in a file of the application's repository: the host reads it from its secrets vault or its environment (omniauth.yaml's totp.secret_key, config/omniauth.php's), base64 (fromBase64()), and both hosts of one database share it. Key rotation: docs/otp.md.
const PREFIX = 'otp:v1:'
SodiumSecretBox::__construct()
public function __construct(string $key)$keythe 32 raw bytes of the key
SodiumSecretBox::fromBase64()
public static function fromBase64(string $key): selfFrom the key as a host keeps it: base64 (standard or URL-safe, padded or not) of 32 bytes.
SodiumSecretBox::generateKey()
public static function generateKey(): stringA new key, in base64: what goes into the vault (php -r "require 'vendor/autoload.php'; echo Omniauth\Otp\SodiumSecretBox::generateKey();").
SodiumSecretBox::isSealed()
public static function isSealed(string $stored): boolWhether a stored value is sealed (by this format), without opening it.
SodiumSecretBox::seal()
public function seal(string $secret): stringSodiumSecretBox::open()
public function open(string $stored): string- Throws
UnreadableSecret a sealed value that does not open under this key
TotpMethod class
final class TotpMethod implements SecondFactor, Enrolling, Revocable TotpMethod.php:40
An authenticator application (TOTP, RFC 6238) as a second factor, over spomky-labs/otphp - the library scheb/2fa verifies with, on the parameters omnibase's User gives it (SHA1, 30 seconds, 6 digits) and the leeway every site sets (0): a code scheb accepts, this accepts, and the secrets kept today (user.secret, base32) go on working. One thing more than scheb: a code is accepted once, and none older than the last one accepted (Contract\UsedSteps).
verify(): ['code' => '123 456'] (spaces ignored). Enrolment: enrolBegin() draws a secret as scheb does and gives it with its otpauth:// address (Challenge::$data: 'secret', 'uri' - the host draws the QR code of 'uri'); enrolConfirm() checks a first code against it and keeps it in the store. revoke() forgets it. Secrets go through Contract\TotpSecrets; wrapped in SealedTotpSecrets with a SodiumSecretBox they are sealed at rest (QA5).
const ALGORITHM = 'sha1'scheb/2fa's TOTP, as omnibase's User configures it and every site's scheb_two_factor.yaml leaves it.
const PERIOD = 30const DIGITS = 6const LEEWAY = 0
TotpMethod::__construct()
public function __construct(readonly Accounts $accounts, readonly TotpSecrets $secrets, readonly UsedSteps $used, readonly int $leeway = self::LEEWAY, readonly int $period = self::PERIOD, readonly int $digits = self::DIGITS, readonly string $algorithm = self::ALGORITHM, readonly ?string $issuer = null, readonly ?string $server = null, ?ClockInterface $clock = null)$leewayseconds a code is accepted before and after its period (scheb's
leeway), lower than the period$issuerscheb's
issuer: shown by the application$serverscheb's
server_name: appended to the label, "ringo@example.org"
TotpMethod::factor()
public function factor(): FactorTotpMethod::enrolled()
public function enrolled(object $account): boolTotpMethod::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictTotpMethod::enrolBegin()
public function enrolBegin(Attempt $enrolment, Proof $proof, Context $context): ChallengeTotpMethod::enrolConfirm()
public function enrolConfirm(Attempt $enrolment, Proof $proof, Context $context): EnrolledTotpMethod::revoke()
public function revoke(object $account): voidTotpMethod::uri()
public function uri(object $account, ?string $secret = null): ?stringThe otpauth:// address an application scans, the same scheb/2fa's TotpAuthenticator::getQRContent() gives: the label is the account's identifier (omnibase's getTotpAuthenticationUsername()), "@" and the server name when one is set; the issuer when one is set. Of the account's own secret when none is given (the settings' QR code), or null when it has none.
TotpMethod::code()
public function code(string $secret, ?int $timestamp = null): stringThe code of $secret at this moment (or at $timestamp): what the application shows.
TotpMethod::secret()
public static function secret(): stringA new secret as scheb/2fa draws it: 32 random bytes, base32, upper case, unpadded (52 characters).
Omniauth\Otp\Contract
SecretBox interface
interface SecretBox SecretBox.php:12
The secrets at rest (Marco's decision QA5, 2026-10-20: the TOTP secret encrypted with sodium, its key in the secrets vault). SodiumSecretBox is the box; SealedTotpSecrets wraps the application's TotpSecrets store with it, and TotpMethod never knows. Both hosts (omnibridge/symfony, omnibridge/laravel) build it from the same key.
SecretBox::seal()
public function seal(string $secret): stringWhat the store keeps for this base32 secret.
SecretBox::open()
public function open(string $stored): stringThe base32 secret of what the store keeps. A box must read back the secrets kept before it existed (in clear) until every row is sealed: it tells them apart (a prefix, a version byte) and returns them as they are.
UsedSteps interface
interface UsedSteps UsedSteps.php:11
The time steps whose code an account already used: a code is accepted once (RFC 6238 §5.2), and none older than the last one accepted. Omniauth\Otp\CacheUsedSteps keeps them in any PSR-6 pool or PSR-16 cache; a host may keep them anywhere else (a column, its rate limiter's store).
UsedSteps::claim()
public function claim(string $handle, int $step, int $ttl): boolClaims the code of $step for the account of $handle: true the first time, false when that step or a later one was already claimed.
$ttlhow long, in seconds, the claim must be remembered: past it the code of that step no longer verifies anyway
Omniauth\Otp\Exception
UnreadableSecret class
final class UnreadableSecret extends RuntimeException implements OmniauthException UnreadableSecret.php:13
A sealed secret that does not open: sealed under another key (a key rotated without its rows, a host on the wrong key), or altered. Never answered as "no secret": an account would then sign in without the second factor it set up. The message names no key and no secret.
Omniauth\Otp\Testing
AuthenticatorApp class
final class AuthenticatorApp AuthenticatorApp.php:15
An authenticator application in PHP, for tests: it scans the otpauth:// address an enrolment gives (or takes a secret) and shows the code of the moment on the clock given - the code a person would type.
AuthenticatorApp::scan()
public static function scan(string $uri, ClockInterface $clock): selfThe application after scanning the QR code of $uri.
AuthenticatorApp::of()
public static function of(string $secret, ClockInterface $clock, int $period = 30, int $digits = 6, string $algorithm = 'sha1'): selfThe application with a secret typed in (SHA1, 30 seconds, 6 digits unless told).
AuthenticatorApp::code()
public function code(int $periods = 0): stringThe code shown now - or $periods periods from now (-1: the one before).
AuthenticatorApp::secret()
public function secret(): stringAuthenticatorApp::label()
public function label(): ?string