Authentication

This page explains what signing in actually is in Zen IdP: how users get credentials, how they use them, what happens when a device is lost, and how sessions behave. It is written for both operators and anyone answering user questions.

Credentials that are derived, not stored

Every user signs in with two things they have: an identifier and a six digit code from an authenticator app. The code is a standard TOTP value, RFC 6238, with a 30 second step, generated by any authenticator: Aegis, 1Password, Bitwarden, Google Authenticator, or the one built into your password manager.

The interesting part is where the shared secret comes from. There is no table of secrets anywhere. Each user's secret is derived on demand from two inputs:

  • the root secret you keep in your environment, and
  • the user's sub and their current TOTP revision.

The same two inputs always produce the same secret, on every restart, forever. That is what makes recovery in this system so short: nothing to back up, nothing to migrate, nothing to restore. It also means the root secret is the one value that protects every credential, which is why Security spends so much time on it.

Enrollment: giving a user their first credential

A user cannot sign in until they have scanned their secret into an authenticator, and that happens through a one-time link:

  1. An administrator opens the admin interface, finds the user, and creates an enrollment token with a lifetime, for example one hour.
  2. The admin interface produces a single link, valid once, bound to that user and their current revision.
  3. The user opens the link and sees a QR code and the secret in text form.
  4. They scan it with their authenticator. The app now generates the right codes.
  5. The link is consumed the moment the secret is shown. Opening it again does not work.

Two properties make this safe by default. The link works exactly once, so a copy circulating in a chat log is dead after first use. And the secret is only ever revealed to whoever holds the unexpired, unconsumed link, which is why you deliver it over a channel you trust for that person.

Enrollment links expire quickly on purpose. If a link goes stale, create a new one, they are free. If you suspect a link was opened by the wrong person, see recovery below.

Signing in

Sign-in always happens inside an application's login flow, which is what makes it single sign-on:

  1. The user clicks sign-in in any connected application and is redirected to Zen IdP.
  2. If they have a live session, they are sent straight back, signed in.
  3. Otherwise they enter their identifier, which is their sub or their idp_login when one is configured, and the current code from their authenticator.
  4. On success, a session is created and the browser returns to the application, carrying an authorization code it exchanges for tokens.

Codes from the authenticator are accepted within a small clock tolerance window of one step in each direction, so a phone a few seconds behind still works. Failed attempts are rate limited per identifier, five attempts by default inside five minutes, and the limit applies to both of a user's identifiers as one bucket. When the limit trips, sign-in for that identifier is throttled for the rest of the window.

An accurate clock is a hard requirement on both sides: the server needs reliable time to validate codes, and the user's device needs it to generate them. If everyone's codes are suddenly rejected, check server time first.

Sessions

After a successful sign-in, the browser holds one Zen IdP session, valid across all your applications for the configured lifetime, 72 hours by default. That is the single sign-on part: the second application redirects, sees the live session, and sends the user back signed in without asking anything.

A few facts that answer most session questions:

  • Signing out of Zen IdP is an action the application or the user can trigger, and it revokes the session server side. It does not sign users out of applications' own local sessions, those belong to the applications.
  • Signing out of an application usually ends only that application's session. The Zen IdP session may still be alive, so the next sign-in there is silent.
  • Any change that matters revokes sessions. Removing the user, letting them expire, incrementing their TOTP revision, locking them, or their own panic action all end the session immediately.
  • Applications' tokens are short and independent. ID tokens and access tokens live 15 minutes and stay mathematically valid until then even if the session is revoked. Applications that need stronger revocation check /userinfo, which enforces session state, or keep short local sessions.

Recovery when a device is lost

People lose phones. Recovery is a short, well-defined procedure, and every step is something you already know how to do:

  1. Stop the bleeding if needed. Lock the user from the admin interface. This ends their sessions and blocks sign-in until you unlock.
  2. Rotate the credential. Increment the user's idp_totp_rev in YAML and deploy. The old secret is now wrong, on the lost device and anywhere else it might have leaked.
  3. Enroll the new device. Create a fresh enrollment token, deliver it, have the user scan it.
  4. Unlock if you locked them in step 1.

Total effort: one number changed, one deploy, one link. No user is ever "locked out forever", because credentials are derived from configuration, not stored in a database you would have to repair.

The same procedure, minus the urgency, is how you rotate a credential proactively when someone changes devices or when policy says so.

The panic action

Sometimes it is the user who notices something wrong, a session they do not recognize, a phone gone missing. For that, any signed in user can open <issuer>/panic, for example https://auth.example.com/panic, and confirm the emergency action.

Invoking it does two things at once, atomically: every session of that user is revoked, including the one they are using, and a temporary panic lock blocks any new sign-in. The user ends signed out and the account is frozen.

The panic lock is deliberate friction, not a punishment: it can only be cleared by an administrator after the organization's checks, which for most deployments means verifying the person and walking them through enrollment on a new device. See Administration for the admin side of locks.