Skip to content

Configure authentication

Configure sign-in methods, credentials, origins, security policies, and session lifetimes.

Set your product’s sign-in methods, sign-up rules, password and MFA policies, passkeys and session lifetimes in Users → Configuration. Saved changes apply to the next authentication flow. Origins and callback allowlists use the configuration API; keys have separate management screens.

Before you start

  • Lepsto Users installed on your product and permission to edit its configuration. Agents need product-scoped admin access for users_config_upsert.
  • For email sign-in, a sender on a domain verified in the product’s mail toolkit.
  • For production OAuth, your own registered Google or GitHub application credentials.

Make a change

Change only the settings you need. The screen has Signup, Password policy, Multi-factor authentication, Passkeys, Sessions, Email, OAuth providers and Waitlist widget sections.

  1. Open Users → Configuration and locate the relevant section.
  2. Edit its fields. Changes remain a draft until saved.
  3. Select Save changes to apply the changed fields, or Discard to abandon the draft.

Settings apply only to this product. Agents can read them with users_config_get and change them with users_config_upsert. Origin and redirect allowlists have no fields on this screen; their API names are given below. Users publishable keys and product public keys are managed separately.

Turn on a sign-in method

Enable the methods your customers need. Several methods can appear on the same sign-in screen, and users can add another method after creating an account.

Password: an email address and password, hashed with argon2id and checked against the policy below.

For either email method, first set email.from in Email to a sender on a verified mail domain. Neither method is offered until that sender exists.

Email one-time code: sends a six-digit code to enter in your form. Codes expire and allow limited attempts. Resending has a cooldown of about one minute and a daily cap.

Magic link: sends a link that opens a confirmation page. Clicking its button consumes the link; merely opening it does not, so mail scanners cannot use it. On another device, the user confirms the waiting device and transfers a short code to it. The session is created on the device that started sign-in.

OAuth: supports Google and GitHub. Shared Lepsto credentials are available for development. In production, register your own provider application and save its client ID and secret in OAuth providers; the secret is encrypted.

Passkeys: let the user authenticate with a WebAuthn credential on their device, often without typing an identifier. Enable them through the passkeys block below. Enrollment is self-service; an operator cannot enroll on someone else’s behalf. Passkeys can also satisfy a second-factor challenge. See Add passkeys.

Multi-factor authentication: offers authenticator apps (TOTP) and single-use backup codes. A factor counts after the user confirms it. Once enrolled, it gates password, email code, magic-link and OAuth sign-in; password reset does not bypass it. An operator reset requires identity verification, a delay, an audit record and an email allowing the user to stop it. See user management and MFA enrollment.

Choose who may sign up

Choose one mode in Signup:

Mode
Who creates the account
Public
Anyone may create an account. The default.
Invite-only
Accounts are created by you, by inviting a person from the directory screen or by running an import, and the user sets their first credential from the invitation email.
Waitlist
A public sign-up form collects people without creating a way in; you invite them in batches later. See Run a waitlist.

Use the API to allow or block email domains, reject disposable addresses or block + sub-addresses. These restrictions apply independently of sign-up mode. The Signup section edits only signupMode.

CAPTCHA is enabled by default for sign-up and is also triggered by repeated sign-in failures for an account or address.

Set the password policy

Edit Password policy to control sign-up and password changes:

Setting
Default
Minimum length
8 characters
Maximum length
at least 64 characters, never silently truncated
Breached-password check
on

The policy uses length rather than required digits, symbols or mixed case. Breach checking sends neither the password nor its full hash to the public breach database. If that service is unavailable, registration can continue. The check runs again after sign-in and asks affected users to change their password.

Repeated failures trigger temporary lockouts by account and source. A lock expires automatically, and an email lets the user unlock sooner. Breach checks, lockout ceilings and rate limits are in the configuration’s protection fields.

Set the second-factor policy

Edit the mfa fields in Multi-factor authentication, or use users_config_upsert. Fields merge individually, leaving omitted values unchanged.

Field
Default
Range
What it decides
required
false
—
Whether a second factor is mandatory for this product
backupCodeCount
10
1–20
How many single-use backup codes a batch contains
resetDelayHours
72
0–720
The delay on an operator-initiated factor reset — the window the user is emailed to abort it
sensitiveAuthAgeMinutes
10
1–1440
How recently the caller must have authenticated to enrol or regenerate a factor
maxSecondFactorAttempts
5
1–50
Wrong second-factor codes before the account is locked
maxBackupCodeAttempts
5
1–50
Wrong backup codes before the account is locked, counted separately from the app’s codes
stepUpGrantMinutes
5
1–60
How long a passed step-up challenge stays spendable

A step-up grant authorizes one use. stepUpGrantMinutes sets how long the user has to use it, not how many operations it permits.

MFA enrollment is always available; there is no product-wide off switch that removes enrolled factors. With required: false, users opt in.

Setting required: true prevents users with a verified factor from removing their last factor, returning last_factor_required. It neither enrolls existing users nor locks them out. Separately, last_way_in prevents any user from removing their last sign-in method.

A shorter sensitiveAuthAgeMinutes asks for fresh authentication more often during enrollment and regeneration. A longer interval allows an already-open session to perform those operations for longer. The default is ten minutes.

Turn on passkeys

Edit Passkeys, between the MFA and Sessions sections, to configure your product as a WebAuthn relying party. The API’s passkeys fields merge individually too.

Field
On the screen
Default
What it decides
enabled
Offer passkeys
false
Whether this product accepts passkeys at all. Off, the method is not offered and no credential can be enrolled
rpId
Relying party ID
none
The domain credentials are bound to, when your allowed origins cannot decide it alone
rpName
Relying party name
none
The product name the operating system’s passkey prompt shows. Defaults to the domain
requireUserVerification
Require user verification
false
Refuse an assertion the authenticator did not verify. A verified one is aal2 either way

Empty rpId and rpName boxes request derived values. Enter only a bare domain for rpId: no scheme, port or path. For example, use example.com, not https://example.com. Replace this example with your product’s domain.

The relying-party domain is resolved in this order:

  1. If passkeys.enabled is false, passkeys are not offered.
  2. If rpId is set, it must match or be a parent of every allowed-origin host. A parent matches whole domain labels: example.com covers app.example.com, but not notexample.com.
  3. Without rpId, one host shared by all allowed origins can be used.
  4. If no unambiguous domain can be resolved, passkeys stay off.

requireUserVerification defaults to false. Without it, an assertion lacking device user verification still counts as one factor and follows the usual second-factor rules. Enabling it rejects such assertions, including those from security keys that cannot verify the user.

There is no passkey required field or per-user enable switch. Your product decides whether to accept passkeys; each user enrolls their own. The authenticator’s result determines the assurance level for each sign-in.

Allow your origins and redirect URIs

Set exact allowed origins and callback addresses, including scheme, host and port. Wildcards and prefix matching are not supported.

allowedOrigins lists the origins serving your frontend. A browser request using upk_ is rejected unless its origin is listed.

redirectUriAllowlist lists permitted flow destinations: sign-in callbacks, OAuth return pages and post-verification pages. A flow cannot return to an unlisted address.

Updating either list replaces it. Include all entries you want to retain.

Maintain separate development and production entries, and remove entries for environments you no longer use.

Rotate a key

Users uses two different credentials. The browser publishable key starts flows; the backend product public key redeems handoff codes.

Publishable key
Product public key
Prefix
upk_
lpk_
Created in
Users → Keys
Management API for route scope; Product → Settings → Public Access for toolkit scope
Runs in
Browser
Backend only
Secret
No
Yes; shown once
Authorizes
Sign-up and sign-in flows
POST /code/exchange
Checked by
Users and its origin allowlist
Platform public edge and key scope

SDK clients derive the product URL from the configured product ID. The browser client refuses lpk_ credentials, and code exchange refuses a publishable key. Refresh uses the refresh token alone, local verification needs no credential, and user administration requires your platform identity rather than either key.

In Users → Keys, API keys lists publishable keys. Issue an API key creates a named key, and each row can be revoked; publishable values remain readable. Legacy usk_ server keys are listed as revoked and authenticate nothing. Code exchange now requires lpk_.

As an Owner or Admin, create the backend public key through the management API. Limit its scope to the exchange route:

{ "extension": "users", "method": "POST", "path": "/code/exchange" }

The Public Access UI selects all or selected toolkits; it cannot select an individual route. A route-scoped key appears there as limited to specific routes. Saving it through the toolkit-level Edit scope editor replaces its route scope and widens access.

Use one key per backend. To replace it:

  1. Create a replacement through the management API with the same exchange-route scope and save its secret.
  2. Wait about 30 seconds for the edge snapshot to refresh, then deploy the replacement to your callers. Both keys remain valid during the changeover.
  3. Revoke the old key in Public Access after callers have switched. There is no separate rotation operation.

The separate JWT signing keys section in Users → Keys lists the keys that sign access tokens:

State
What it means
Standby
Generated, not published, verifying nothing yet
Current
Signs every token from now on
Retired
No longer signs, but stays in JWKS until its overlap expires
Revoked
Evicted from JWKS, so tokens it signed no longer verify

Rotate signing key creates a new current signer. Retired keys stay available during the overlap, so rotation alone does not invalidate existing tokens. Revoke a key from its row to remove it from JWKS. Agents can perform these operations too.

Set session lifetimes

Edit these values in Sessions:

Setting
Default
Notes
Access token lifetime
10 minutes
accessTokenTtlMinutes; it cannot go below 5 minutes
Session lifetime
720 hours
sessionTtlHours — the session’s own lifetime
Session inactivity window
30 days
refreshSlidingDays; every refresh slides it forward
Session absolute lifetime
365 days
refreshAbsoluteDays, counted from sign-in and never extended

Shorter access-token lifetimes reduce the delay before local verification stops accepting a revoked session, at the cost of more refreshes. Checked verification tests the live session with one additional call when you need immediate revocation. See sessions and tokens.

Password changes, email changes, MFA resets, bans and erasure end sessions regardless of configured lifetimes.

Verify it worked

Start a new flow after saving, because existing attempts do not demonstrate the new configuration:

  1. Open sign-in in a fresh browser session and check the offered methods.
  2. Complete an enabled method. If it fails, inspect the flow error and check the sender, provider credentials and origin allowlist.
  3. Test the affected boundary: for a password minimum change, confirm an undersized password is rejected at sign-up.

Next steps

Was this page helpful?
Esc

Start typing to search the docs.

↑↓navigate↵select