Skip to content
Zeitlos
Dashboard

Authentication

.md

Local development

Run authentication on your own machine, or point it at your real production users.

The Zeitlos CLI runs a local auth service, so sign-up, sign-in, verification and password reset all work on your machine without touching production.

Terminal
npx @zeitlosapp/cli dev

Requires Node 22.5+. It writes ZEITLOS_AUTH_URL into .env.local for server-side use, and the auth service listens on port 7311 by default.

Authentication emails

While using the emulator authentication emails won't be sent. Instead you'll see the link that would be sent printed to the console where you started npx @zeitlosapp/cli dev.

Password resets

To test the password reset page, you'll need to configure the path locally in the zeitlos.dev.json file at the root of your repository. Set the auth.resetPasswordUrl to the URL that should be used:

zeitlos.dev.json
{
  "auth": {
    "resetPasswordUrl": "http://localhost:3000/reset-password"
  }
}

Testing against real users

Sometimes the emulator's isolated users aren't what you want and you'd rather sign in as a real deployed user:

zeitlos.dev.json
{
  "project": "my-app",
  "auth": { "useProductionAuth": true }
}

The local auth server then proxies to <slug>.ztls.app instead of emulating. Nothing in your code changes — it still talks to the local emulator — but sign-in creates a real production session and getUser() returns the real user.

Warning

Because this is production auth: real emails are sent, and their links point at your production origin rather than localhost, so the terminal no longer prints them. The other auth.* settings in zeitlos.dev.json are ignored. Your project's real settings apply.

Settings

Local development is configured in zeitlos.dev.json at your project root. These are the same behaviours you'd set under Authentication → Settings in the dashboard, applied locally.

zeitlos.dev.json
{
  "auth": {
    "enabled": true,
    "signUpEnabled": true,
    "requireEmailVerification": false,
    "resetPasswordUrl": "http://localhost:3000/reset-password",
    "verifyCallbackUrl": "http://localhost:3000/welcome"
  },
  "ports": { "auth": 7311 }
}
KeyPurpose
auth.enabledCan be used to disable the auth emulator. true by default
auth.signUpEnabledAllow sign-up
auth.requireEmailVerificationRequire verification before sign-in
auth.resetPasswordUrlWhere the printed reset link points — reset stays disabled until set
auth.verifyCallbackUrlWhere the verify link lands after verifying
auth.useProductionAuthProxy to your real project's auth instead of emulating
ports.authRun auth on a port other than 7311. Note: Changing the port requires more changes to your app outline below.

Set resetPasswordUrl to your local reset page so the link printed to the terminal actually lands somewhere. See Password reset.

Note

The file configures local development only. Nothing in it affects your deployed project. The database has its own keys in the same file; see Database → Local development.

Troubleshooting

Auth calls go to the wrong port

This is the one case the SDK cannot work out on its own. In the browser it can't read ZEITLOS_AUTH_URL — that's a server-only variable — so it assumes the emulator's default port 7311. If you changed ports.auth, the browser keeps looking at 7311 and every call fails.

Tell the client where to look:

lib/zeitlos.ts
import { createClient } from '@zeitlosapp/sdk';

export const client = createClient({
  // Unset in production, where same-origin resolution takes over — so the same
  // code works in both places.
  authUrl: process.env.NEXT_PUBLIC_ZEITLOS_AUTH_URL,
});

Expose the port to the browser using whatever prefix your framework requires (NEXT_PUBLIC_, VITE_, …):

.env.local
NEXT_PUBLIC_ZEITLOS_AUTH_URL=http://localhost:7350

The same applies to any dev host that isn't localhost.

forgetPassword throws RESET_PASSWORD_NOT_CONFIGURED

Reset is disabled until a reset page is configured. Locally that means setting auth.resetPasswordUrl in zeitlos.dev.json — the dashboard setting only covers production.

Sign-in throws EMAIL_NOT_VERIFIED

requireEmailVerification is on. Either verify using the link printed to your terminal, or set auth.requireEmailVerification to false while you build.