Skip to content
runlot

Email login

Set up email and password login and magic links. Default login screens are included.

Your app has login and sign-up screens as soon as your worker calls env.auth. You do not have to build them yourself.

PathScreen
/__runlot/auth/sign-inLogin
/__runlot/auth/sign-upSign-up
/__runlot/auth/forgotForgot password
/__runlot/auth/resetReset password
/__runlot/auth/signed-outSigned out

Your worker only receives requests that do not start with /__runlot/auth, and it handles those requests exactly as before.

Using your own login screens

To use your own forms instead of the default screens, post to these paths.

await fetch("/__runlot/auth/sign-up/password", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ email, password }),
});

await fetch("/__runlot/auth/sign-in/password", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ email, password }),
});

POST requests sent from a browser must carry an Origin header (or a Referer if there is no Origin) pointing at the same host. If neither header is present, the request is rejected.

Instead of a password, send a login link by email.

await fetch("/__runlot/auth/sign-in/email", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ email }),
});

When the user opens the link they receive, a session is created immediately.

An email with no account gets a link too while sign-up is open. Opening it creates the account with the email already verified. With sign-up closed, nothing is sent to an unknown email. The answer is the same either way, so the request does not reveal whether an account exists.

Password rules

  • At least 8 bytes, at most 256 bytes
  • Hashed with argon2id (19 MiB of memory, 2 iterations)
  • Plaintext passwords are never stored

Login attempt limits

After 10 failed logins in 15 minutes from the same email address, or 30 failed logins in 15 minutes from the same IP address, requests are blocked for a while. The counters live in node memory, so they reset when the process restarts.

Settings

runlot auth set allow-signup off        # Disable new sign-ups (default: on)
runlot auth set require-verified on     # Block login until the email is verified (default: off)
runlot auth set session-days 7          # Session lifetime (default: 30 days)
runlot auth set brand-name "My App"
runlot auth set brand-color "#0f6f8f"
runlot auth set brand-logo https://example.com/logo.svg

The branding settings are applied to the default login screens.

How email is sent

In the default development environment, login links are written to the project logs instead of being emailed. You can read them with runlot logs. For how mail is actually sent, see Sending email.

On this page