Build the otpauth:// QR code that authenticator apps read (TOTP and HOTP, entirely in your browser)
What an authenticator app reads out of the QR code is a single URL: otpauth://totp/{issuer}:{account}?secret={Base32}&issuer={issuer}. The format is published as the Google Authenticator Key URI Format, and most authenticator apps accept the same shape. The totp segment is the type: use totp for time based codes and hotp for counter based ones.
The shared secret is written in Base32 (A to Z plus 2 to 7). That alphabet drops the characters most easily confused with each other, so if you see a 0, a 1 or an 8 in your secret it is probably a typo. This tool upper cases your input and strips anything outside the Base32 alphabet before using it. Trailing = padding is optional. Twenty bytes, which is 32 Base32 characters, is the common length.
The defaults are SHA1, 6 digits and a 30 second period. Defaults carry the same meaning whether or not they appear in the URI, so this tool only adds a parameter when you change it. One warning: some authenticator apps do not support SHA256, SHA512, 8 digits, or a period other than 30 seconds. Even if your server implements them, an app that ignores the parameter will compute SHA1, 6 digits and 30 seconds instead, and the codes will never match. If you change any of these, verify once with the actual app before you hand out the QR code.
TOTP divides the current time by the period (30 seconds by default), so the code changes on a fixed interval. HOTP uses a counter that advances by one on every use. It needs no clock synchronisation, but if the server side and the app side drift apart the codes stop matching. Two factor authentication on ordinary web services is almost always TOTP.
No. Everything from input to QR generation and download runs inside your browser in JavaScript. The secret is never sent to a server. That said, a shared secret carries the same weight as a password, so be careful with the QR image you generate.
It draws 20 bytes from the browser cryptographic random source (crypto.getRandomValues) and converts them to 32 Base32 characters. Nothing is derived from the clock or a counter. If you use the value in production, remember that the same value has to reach your server over a safe channel as well.
There are three common causes. First, the wrong secret: check that no character outside the Base32 alphabet slipped in. Second, device clock drift: TOTP uses the current time, so a few tens of seconds of drift breaks it. Third, changing algorithm, digits or period away from the defaults, which some authenticator apps silently ignore and compute with the defaults instead.