Skip to content

Sign-in

Make people sign in before they reach an App, with passwords, GitHub, Google or your identity provider, and manage their accounts in the App.

examples/apps/lintel/app.ts
auth: {
// …
providers: ['email'],
// …
roles: {
leasing,
manager,
// …
admin: { label: 'Admin', extends: 'manager', operations: ['workflows'] },

See it in the sample (examples/apps/lintel/app.ts:54): the admin role is the account a first start bootstraps (TABLEWALK_AUTH_ADMIN_EMAIL).

Lintel’s sign-in card: Sign in to Lintel, an email and a password field, a Sign in button and Forgot password?.

  • A sign-in card at every address of the App until a session exists; the server reads a signed-out visitor nothing but what auth.public names.
  • An account menu in the bar: who is signed in, Your profile (where a person changes their name, password and, when the App sends email, their address: a link goes to the new address and a notice to the old one), preferences, dark mode and Sign out, which locks and clears the App at once.
  • For an account holding admin, Manage accounts in that menu.
  • Use auth whenever the App is reached beyond your own machine: without it, every column of every declared resource goes to anyone who reaches the port.
  • Use roles beside it when people differ in what they read and write; auth without roles signs people in and gives them all the same access.
  • Use an identity provider instead of passwords when your organization already signs people in.
Key What it does Default Status
providers 'email' (password), 'github', 'google', oidc('<name>'), saml('<name>') ['email'] Stable
signUp 'invite' (an administrator adds accounts), 'open', or { providers, domains } 'invite' Stable
defaultRole The role a new account gets 'user' Stable
roles What each role reads, writes and runs (roles) none: all alike Stable
public What a signed-out visitor reads (roles) nothing Stable
me The table and email column that are the signed-in person ({ from: 'member', column: 'email' }); in a tenant App the column holds the account’s id, and me is the row in the tenant the person is working in none Stable
me.create { name: 'full_name' }: the server makes a new account’s row on its first signed-in request (sign-up, invitation, first single sign-on), once; the email column must be unique none Stable
store Where accounts and sessions live: a SQLite file or a postgres:// URL, apart from the business data auth/<id>.db in the config directory Stable

All keys: the generated reference. Changing auth needs a restart.

  • BETTER_AUTH_SECRET in its environment, at least 32 characters; without it the App does not start.
  • The address people reach it at, when that is not loopback: --allowed-host (build and deploy).
  • A first account: TABLEWALK_AUTH_ADMIN_EMAIL and TABLEWALK_AUTH_ADMIN_PASSWORD make it, with admin, at the first start; or signUp: 'open'. Each example’s README shows its seed step.
  • For password resets and verified addresses, an email service. Without one, TABLEWALK_AUTH_RESET=log writes each reset link to the server’s log for an operator to pass on; set it only where the log is as private as the secret.
  • With signUp: 'open', an email service too. Anyone may claim any address there, so an account someone makes for themselves is matched to me (and to the rows a row policy finds by its email) and given notifications only once its address is verified; until then it is signed in as nobody in particular. An account an administrator makes on the accounts screen is verified when it is made: the administrator named the address, and no link is sent for it. tablewalk check fails on open sign-up with no email service (open-sign-up-unproven).

An account holding admin adds accounts (name, email, roles and, with passwords, a first password), sets roles, deactivates and reactivates them, and sets a new password. Deactivating or a new password ends the account’s other sessions. Each change is authorized from the verified session; an administrator cannot remove their own admin or deactivate themself. admin manages accounts; what it reads and writes in the App comes from the admin entry in roles, as for any role.

Every change is recorded in the sign-in store in the same transaction, so a change that cannot be recorded is not made: who, when, the account, and the fields before and after, never a password or a token. History on the accounts screen reads it. A tenant App manages members through its own administration.

A one-row resource such as a company profile opens from Settings in the account menu, on its own page and under its own grants: company: { access: 'write', settings: 'Company profile' }.

  • The shell sends a Content-Security-Policy: scripts from the server only, connect-src 'self', no framing by other sites.
  • A signed-in reader’s pictures are private, no-cache with Vary: Cookie, so a withdrawn grant or a sign-out takes effect at once.
  • History, the activity feed and notices name people, never their email.
  • What a browser already fetched is not erased from it; there is no policy polling.
  • "auth" needs BETTER_AUTH_SECRET in the environment → set a random value of 32 characters or more, kept like a password.
  • Nobody can sign in to a new App → set TABLEWALK_AUTH_ADMIN_EMAIL and TABLEWALK_AUTH_ADMIN_PASSWORD for the first start, or open sign-up.
  • A new account sees an empty App → defaultRole names a role roles does not declare (the default is user); declare it or name one that is. [role-label] → give each role a label, the name people read on the accounts screen.
  • “Sign-in failed (500)” and nothing else → the server’s stderr has one line for it, tablewalk: auth POST /api/auth/sign-in/email answered 500 for App "<id>": <error>: the route, the error’s name and message, never the email, password, token or cookie. A wrong password is a 401 and is not logged.