Skip to content

Signing in

After your application has checked the password, call beginSecondStep with the user’s ID, the browser’s details and, if it has one, the remembered-device cookie. Keep the SecondStep it returns in the server-side session: never in a cookie or a hidden field.

SecondStep step = tenonAuth.beginSecondStep(account.id(),
new SignInContext(request.getRemoteAddr(), request.getHeader("User-Agent")),
cookie(request, "tenon_device"), locale.getLanguage());
kind Page Then
CODE_ENTRY The code page: one field for 6 digits. With method EMAIL, say where the code went (emailSentTo, masked) and offer Send a new code (call beginSecondStep again, after 30 seconds) completeWithCode
PUSH_WAIT The waiting page: “Approve the sign-in on your phone, and pick 42” (number). It reloads every 2 seconds, or polls with JavaScript pollApproval until it isn’t PENDING
TRUSTED Nothing: a remembered browser Sign in
NO_METHOD The user has no method yet. Your policy: sign in with the password alone, or send them to set one up first
RECOVERY_ONLY No email could be sent. The recovery page only completeWithRecoveryCode

On the code and waiting pages, add a link Lost your phone? Use a recovery code. It leads to the recovery page: one field for a recovery code or a helpdesk access pass, then completeWithRecoveryCode.

status What to do
APPROVED Sign in. Then remember the browser if the user ticked the box (below)
WRONG_CODE Show the page again: “Wrong code, attemptsRemaining tries left”
PENDING Sign-in approval: keep waiting
DENIED The user said on the phone that it wasn’t them. End the sign-in, and suggest they change their password: someone else knows it
FAILED Too many wrong codes: back to the password
EXPIRED Too slow: back to the password

A FactorLockedException / FactorLockedError means 10 wrong codes in a row: the method is locked until lockedUntil. Show that time, and offer the recovery page.

Offer a checkbox on the code and waiting pages. When the user ticked it and the step is APPROVED:

TrustedDevice device = tenonAuth.rememberDevice(step.userId(), step.challengeId());
// Cookie "tenon_device" = device.token(): HttpOnly, Secure, SameSite=Lax, until device.expiresAt() (14 days).

When remembered browsers stop working:

  • the user changes or removes their method;
  • an Administrator signs them out of every device;
  • after 14 days.

A token only works for its own user.

language (ja or en) sets the email codes’ language. Without it they are bilingual. Phones show requests in their own language.

  • At most 5 approval requests or emails per user in 15 minutes: a TooManyRequests error with retryAfter.
  • Email codes: a new one at most every 30 seconds.
  • Show these as “please wait”, never as a failed sign-in.