Sign-in for the app's users
How to add sign-in to an app on LazyFrog, from a password-protected admin page with no code to accounts for your app's own users.
Pick the lightest thing that does the job:
- Only the owner or their staff (an admin page, a stock list), or a small group for now (a
club's members' page): protect the paths with
routingauth(a username and password the platform hashes). No code and no user table. Leave the password out: the owner chooses it in the portal (Settings › Protected pages); a new one is in force on the preview and the live site at once. One shared password can't be taken from one person alone, and there's no "sign out": on a shared phone, say that anyone holding it can make changes. The router refuses changes to a protected page that come from another site; checkOriginin the app's own handlers too. Put an owner page's API routes under its path (/admin/api/…), so one rule and one password cover both. - Customers of a shop or salon rarely need accounts: a random manage link in their
confirmation (
/booking/<token>, 128 bits) lets them see or cancel their booking. - People without email (a class of 12-year-olds, staff on a shared tablet): the owner prints a card per person with a random code (three words and a number, 28 bits or more); store only its hash, keep sessions in a table with a sign-out, and let reprinting a card end the old code. Limit failed codes per address generously: a whole classroom shares one school address. Schools with Microsoft 365 or Google Workspace accounts can use those instead (Better Auth has both).
- Visitors with their own accounts: use a maintained library or the framework's own
generator, never password hashing or session logic you invent: the stack's own (Django's auth,
Rails' authentication generator or Devise, Laravel's starter kits (Breeze before Laravel 12), Phoenix's
phx.gen.auth, ASP.NET Identity, SvelteKit'ssv add better-authorsv add lucia, Auth.js, Nuxt's auth modules…) or, on the default Next.js stack, Better Auth (email and password, sessions in the project's PostgreSQL; put the tables its CLI'sgenerate --output migrations/0002_auth.sqlwrites into a migration (the CLI is now theauthpackage:npx auth@<the installed better-auth version> generate --config lib/auth.ts --output migrations/0002_auth.sql --yes;npxfetches it outside the week-old rule, so pin that version); with Google only, setemailAndPassword: { enabled: false }; setBETTER_AUTH_SECRETfor both environments, and mark itbuild: truefor production: the publish rehearsal starts the app with build secrets only, and Better Auth refuses to start without it. Its sign-up limit (a few per 10 s per address) trips a school or an office: setadvanced: { ipAddress: { ipAddressHeaders: ["x-lazyfrog-client-ip"] } }). Set the secret before anyone signs up: sessions signed without it end when it's set) or Auth.js. FastAPI: Argon2 password hashes (pwdlib[argon2]), sessions in a table, a random session id in the cookie. - Cookies:
HttpOnly,Secure,SameSite=Laxand host-only (noDomain, or the__Host-prefix): every site onlazyfrog.appshares that parent domain. A sign-in shared by entry hostnames of the user's own domain is the one exception (Custom domains). - Generators that sign up by email link only (Phoenix 1.8's
phx.gen.auth) can't register anyone while email isn't connected: add a password to registration until it is. - Sign-in links and password resets go out through the owner's email provider; while
email statussays it isn't connected, offer password sign-in and tell the user there's no reset or verification yet, and where they connect one. Never link records to an account by its email address until that address is verified: anyone can sign up with someone else's. - Google or GitHub sign-in: the user makes the OAuth app at the provider and gives you its client
id and secret through
secrets request(once for preview and once for production: tell them a second link comes). Register both callback addresses (preview and the live site). For Google, tell them in plain words: 1) create a project at console.cloud.google.com; 2) Google Auth Platform › Branding: app name, support email, home page and the live site's/privacy; Audience: External, then "Publish app" (in Testing only listed test users can sign in); don't upload a logo (that starts Google's brand review); 3) Clients: a "Web application" with both callback addresses (https://<preview>/api/auth/callback/googleand the live one, the library's path); 4) copy the client ID and secret into your link. The authorised domain islazyfrog.appuntil they connect their own domain; with basic scopes and no logo Google doesn't ask to verify it, and a site meant to last is better on its own domain. Restricting sign-in to a list (a club's members): check Google's verified email against it at sign-up and at every sign-in; tell the owner members can make a Google account for their own address, and that people sharing an address need one each. - Limit sign-in attempts per address in the app (a table of attempts is enough), and never log passwords, tokens or session ids.
- Accounts the app makes from a secret the owner sets (a demo or first admin from
DEMO_PASSWORD; preview-only ones checkLF_ENV, which isprevieworproduction): your own test accounts don't prove those exist. Once the owner says they set it, checkservice_status(it names a secret set after the service started: restart it), then that the accounts are in the database, before you tell them to sign in (2026-11-05: an owner was sent to a sign-in that had no account behind it).
For your AI: this page as Markdown · llms.txt