2FA enrolment codes
Qr::otpauth() accepts the otpauth:// URI another package already built and encodes it unchanged — for example the provisioningUri of two-factor-for-laravel:
$markup = Qr::otpauth($setup->provisioningUri) // an existing otpauth:// URI, encoded unchanged
->size(240)
->errorCorrection(ErrorCorrection::Medium)
->title(__('Scan with your authenticator app'))
->svg()
->toString();Building a URI
Otpauth::totp() delegates to crypto-for-laravel’s provisioning-URI builder, so the result is byte-identical to two-factor-for-laravel’s URIs. hotp() applies the same encoding rules plus the counter:
use RoundlyConsulting\Crypto\Otp\OtpAlgorithm;
use RoundlyConsulting\Qr\Payloads\Otpauth;
// byte-identical to crypto-for-laravel's provisioning URI:
$totp = Otpauth::totp(secret: $secret, account: '[email protected]', issuer: 'Acme', algorithm: OtpAlgorithm::Sha256, digits: 6, period: 30);
$hotp = Otpauth::hotp(secret: $secret, account: '[email protected]', issuer: 'Acme', counter: 0);
$parsed = Otpauth::fromUri($uri); // ->type(), ->issuer(), ->account()
Qr::otpauth($totp)->svg();- fromUri() keeps the given string byte for byte; it checks the otpauth scheme, the totp/hotp type, a non-empty account and a canonical base32 secret.
- Optional algorithm (SHA1, SHA256, SHA512), digits 6–10 and period ≥ 1; hotp requires a counter ≥ 0. URIs are limited to 1024 bytes.
- Issuers containing a colon (e.g. Acme: Admin) parse correctly from the encoded label.
Always secret
2FA codes are always Sensitivity::Secret — never memoised or cached, served with Cache-Control: no-store and no ETag — and the sensitivity cannot be lowered. A raw string starting with otpauth:, otpauth-migration: or WIFI: (after leading whitespace, in any case) passed to any entry point — Qr::make(), Qr::text(), Qr::svg(), <x-qr-code>, or Qr::url() with that scheme opted in — gets the same treatment; WIFI: content is Secret too but may be lowered, like Qr::wifi(). Secrets are marked #[SensitiveParameter], so they never appear in stack traces.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.