Authn.tech
Home
  • SAML 2.0
  • OAuth 2.0
  • OIDC
  • WebAuthn / Passkey
  • MFA / TOTP
  • LDAP
  • All Tools
  • JWT Decode
  • JWT Sign
  • JWK Gen
  • JWK → PEM
  • PEM → JWK
  • PKCE Gen
  • OIDC Discovery
  • TOTP
  • WebAuthn
  • SAML Codec
  • SAML Metadata
  • SAML Response
  • X.509 Cert
  • PEM Inspect
  • Base64URL
  • LDAP Filter
  • Overview & Roles
  • OIDC Mock
  • SAML Mock
  • Mail Server
  • LDAP Directory
  • OIDC Login Demo
  • SAML Login Demo
  • 简体中文
  • English
  • Deutsch
GitHub
Home
  • SAML 2.0
  • OAuth 2.0
  • OIDC
  • WebAuthn / Passkey
  • MFA / TOTP
  • LDAP
  • All Tools
  • JWT Decode
  • JWT Sign
  • JWK Gen
  • JWK → PEM
  • PEM → JWK
  • PKCE Gen
  • OIDC Discovery
  • TOTP
  • WebAuthn
  • SAML Codec
  • SAML Metadata
  • SAML Response
  • X.509 Cert
  • PEM Inspect
  • Base64URL
  • LDAP Filter
  • Overview & Roles
  • OIDC Mock
  • SAML Mock
  • Mail Server
  • LDAP Directory
  • OIDC Login Demo
  • SAML Login Demo
  • 简体中文
  • English
  • Deutsch
GitHub
  • MFA / OTP

    • MFA and One-Time Password Overview
    • HOTP / TOTP Algorithm Deep Dive
    • otpauth URI and Parameter Reference

otpauth URI and Parameter Reference

This page is a quick reference handbook for OTP distribution and parameters: otpauth:// URI format, each parameter's default value, QR code conventions, authenticator compatibility, Base32 encoding rules, and a troubleshooting quick table for "code doesn't match." For algorithm details, see HOTP / TOTP Algorithm Deep Dive.

otpauth:// URI Format

Authenticator apps (Google Authenticator, etc.) import the shared secret via an otpauth:// URI. This is a de facto standard (defined by Google Authenticator and widely adopted). The QR code encodes this exact URI.

otpauth://TYPE/LABEL?secret=...&issuer=...&algorithm=...&digits=...&period=...&counter=...

Components:

  • TYPE: totp or hotp, determining the moving factor type.
  • LABEL: Display name identifying the account, format issuer:account (e.g., Example:[email protected]). The part before the colon is the issuer, after is the account name. LABEL needs URL encoding (colon can be written as %3A, space as %20).
  • query parameters: Key and optional algorithm parameters, see table below.

Complete example:

otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30

HOTP example (note using counter instead of period):

otpauth://hotp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example&counter=0

Parameters and Defaults

ParameterApplies toRequiredAllowed valuesDefaultDescription
secrettotp/hotpYesBase32 stringNoneShared secret, RFC 4648 Base32, uppercase, no padding
issuertotp/hotpStrongly recommendedStringNoneIssuer name, should appear in both LABEL prefix and this parameter, consistent
algorithmtotp/hotpOptionalSHA1 / SHA256 / SHA512SHA1HMAC hash algorithm
digitstotp/hotpOptional6 / 86Number of code digits
periodtotpOptionalPositive integer (seconds)30Time step, TOTP only
counterhotpRequired for HOTPNon-negative integerNoneInitial counter, HOTP only

issuer consistency

Best practice: issuer appears both as the LABEL prefix (Example:alice@...) and as a standalone issuer=Example parameter, with both being identical strings. This way, even if some app only reads one place, it correctly displays the issuer and avoids confusion with same-named accounts.

QR Code Conventions

  • Encode the complete otpauth:// URI as QR code content (plain text), and users scan with their authenticator app to import.
  • Error correction level is usually M (medium); if the URI is long with a lengthy key, can use L to shrink the graphic.
  • QR codes should only be shown during binding, should not be persisted or cached, and cleared from memory after display; avoid appearing in logs, screenshots, or CDN caches.
  • Simultaneously provide the key in Base32 text for manual entry when scanning is not possible.

Mainstream Authenticator App Compatibility

AppNon-default algorithm (SHA256/512)digits=8Custom periodNotes
Google AuthenticatorOlder versions often ignore and use SHA1Unstable supportUnstable supportMost conservative compatibility; recommended to use defaults only
Microsoft AuthenticatorPartial supportPartial supportPartial supportDefaults are most reliable
AuthyGood supportSupportedSupportedHigher parameter tolerance
FreeOTP / andOTPGood supportSupportedSupportedOpen-source implementations, complete parameter support
1PasswordGood supportSupportedSupported—

Interoperability First Principle

Without strong security requirements, stick to the SHA1 / 6-digit / 30-second default combination. Many apps (especially older Google Authenticator) will silently ignore algorithm, digits, period parameters and always compute using defaults—when this happens, the codes it generates will never match your server's SHA256/8-digit calculations, and there's no error message, making it extremely hard to debug.

Base32 Encoding Notes (RFC 4648)

The secret uses Base32 encoding with these conventions:

  • Alphabet: A–Z + 2–7, 32 characters total (omits 0, 1, 8, 9 to avoid confusion with letters).
  • Uppercase: Authenticators display and accept only uppercase; decoding should be case-insensitive.
  • No padding: In the otpauth scenario, remove trailing = padding.
  • Encoding unit: Every 5 bits map to one character, so 8 characters encode 5 bytes.
  • Recommended length: Key at least 160 bits (20 bytes) = 32 Base32 characters.
Bytes (5)  : 0x48 0x65 0x6C 0x6C 0x6F
Base32    : JBSWY3DP

Parameter Defaults Summary

ItemDefault
TYPEtotp
algorithmSHA1
digits6
period30 seconds
T0 (time start point)0 (Unix epoch)
Verification tolerance window±1 step (~±30 seconds)
secret encodingBase32, uppercase, no padding
secret length≥160 bits (20 bytes)

Troubleshooting Quick Table: Code Never Matches

Ordered by frequency of occurrence:

Symptom / CauseHow to verifySolution
Client clock driftMost common. User's device time is inaccurate, or timezone/auto-sync is offEnable device "set time automatically"; server can temporarily widen window to locate offset
Server clock driftServer not synced to NTPDeploy NTP; self-check with standard test vectors
Key encoding errorStored/retrieved key treats Base32 as raw bytes or vice versaBe explicit about key representation in database, URI, HMAC input; HMAC input must be decoded raw bytes
Base32 padding/casesecret contains = or lowercase, causing app parse failureRemove padding, convert to uppercase before writing to URI
Algorithm/digits mismatchServer uses SHA256 or 8-digit, but app ignores parameters and uses SHA1/6-digitAlign to default SHA1/6-digit; or switch to an app with higher parameter tolerance
period inconsistencyServer period doesn't match URI, or app ignored custom periodAlign to 30 seconds
Counter desync (HOTP)User accidentally advances token, causing counter to skipServer sets look-ahead window; sync counter after match
No tolerance windowOnly checking current time slice; fails crossing window boundaryCheck ±1 steps during verification
Replay block false positiveReplay prevention mistakenly rejects legitimate first useReview "last-used time slice" record logic
counter/period field mixupHOTP accidentally uses period, or TOTP uses counterUse correct parameter per TYPE

Self-check Recommendation

Validate your server implementation using HOTP test vector from RFC 4226 Appendix D (key "12345678901234567890", counter 0 → 755224) and TOTP test vector from RFC 6238 Appendix B. This quickly pinpoints whether it's an algorithm implementation issue or a configuration/clock problem.

Back to: MFA Overview · Algorithm Deep Dive

Last updated: 7/6/26, 7:49 AM
Contributors: linux, Claude Opus 4.8
Prev
HOTP / TOTP Algorithm Deep Dive