How TOTP works here
The developer’s backend owns the secret. The U2 Secured Authenticator backend owns nothing.
The whole flow
- 1The developer’s backend generates a random secret with generateSecret() and stores it against its own user record.
- 2The developer’s backend renders that secret as an otpauth:// QR code; the end user scans it with any authenticator app.
- 3At sign-in the end user reads six digits from their app and types them into the developer’s login form.
- 4The developer’s backend loads the secret it stored for that user.
- 5The developer’s backend checks the typed code against that secret — either locally with validateTOTP, or by sending both to the U2 Secured Authenticator backend, which runs the identical check and additionally applies a shared lockout and records the attempt in the Activity feed.
Enrolment never reaches us
Steps 1 and 2 involve no network call to the U2 Secured Authenticator backend at all — generateSecret and generateQRCodeURI are local helpers. Combined with local verification in step 5, an integration can run entirely on the developer’s own infrastructure: we would never learn that the user exists.
Verification is stateless
The U2 Secured Authenticator backend keeps no per-user record for TOTP: no secret, no counter, no enrolment row. Each verification is a self-contained calculation over the secret and code supplied in that request.
That has three consequences worth planning around. There is no enrolment list to browse in the developer portal. There is no server-side limit on how many authenticators you enrol. And restoring a user after a lost phone is entirely your flow, because only your database knows who was enrolled.
You do not have to call us to verify
Every SDK ships validateTOTP, which runs the same RFC 6238 check in your own process and returns the same answer. Since your backend already holds the secret, no round trip is needed to check a code — and verifying locally keeps a third party out of your login path entirely.
What the API call adds
- A shared brute-force lockout that spans every instance of your backend, rather than per-process counters you would have to build and share yourself.
- An entry in the Activity feed of the developer portal, which is the only visibility we have into TOTP usage.
Use validateTOTP when availability matters most — a local check cannot fail because we are unreachable. Use client.verifyTOTP when you want the lockout and the audit trail without building them. Both are supported, and the shared test vectors in the SDK repository assert that the two paths never disagree.
Where the secret travels
Because TOTP is symmetric, the U2 Secured Authenticator backend cannot check a code without the secret — so the developer’s backend includes it in each verification request over TLS. We use it for one comparison, never write it to disk, never log it, and hash it before it is used as a rate-limit key. It is seen, but never stored.