Skip to content
LazyFrog

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 routing auth (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; check Origin in 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's sv add better-auth or sv 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's generate --output migrations/0002_auth.sql writes into a migration (the CLI is now the auth package: npx auth@<the installed better-auth version> generate --config lib/auth.ts --output migrations/0002_auth.sql --yes; npx fetches it outside the week-old rule, so pin that version); with Google only, set emailAndPassword: { enabled: false }; set BETTER_AUTH_SECRET for both environments, and mark it build: true for 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: set advanced: { 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=Lax and host-only (no Domain, or the __Host- prefix): every site on lazyfrog.app shares 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 status says 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/google and the live one, the library's path); 4) copy the client ID and secret into your link. The authorised domain is lazyfrog.app until 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 check LF_ENV, which is preview or production): your own test accounts don't prove those exist. Once the owner says they set it, check service_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