Skip to content

Authentication basics

Authenticate the caller before evaluating permissions. Keep identity verification separate from authorization, use narrowly scoped credentials, and fail closed when the principal or required scope is missing.

Signing in establishes who the consumer is; it does not by itself open a private model. A model whose anonymous_access.read is false grants a signed-in consumer of the same application only what its settings.consumer_reads says:

  • any_authenticated: every eligible row, even when consumer_owner_query is also set.
  • owner: only the rows the model’s consumer_owner_query returns for the consumer; owner without that query is rejected at save time (consumer_reads_owner_requires_owner_query).
  • absent or off: unchanged legacy behavior. A configured consumer_owner_query still scopes the consumer to its rows; without one the consumer reads no private rows.

The same decision applies on reference validation, edit-form loading, model-qualified record get and list, public action targets, page bindings, and record pickers. A consumer signed into a different application is treated as anonymous, and anonymous callers follow only anonymous_access.read, so any_authenticated never opens a model to signed-out visitors and never restricts a model that is already publicly readable. Reading grants nothing else: consumer_writes alone decides consumer create, update, and delete, and a public record action still needs the model’s public_record_actions permission. Model settings use replacement semantics, so send the complete merged settings object. See row-level-security, “Set the consumer read policy”, for the owner-query contract.

Self-registration proves email ownership by default (identity_settings.registration_email_verification is required). Handle all three modes and the duplicate case in every registration form:

  • required: the registration POST answers 202 with registered: true and emailVerification: "pending" and no session. Show “check your email”; do not redirect to a signed-in page. The user follows the mailed link, lands on verification_redirect_path (default /) with email_verification=verified, and then signs in.
  • optional: the usual session response with emailVerification: "sent"; the user is signed in and actor.email_verified stays false until the link is followed.
  • off: the usual session response; the identity starts verified.
  • An email that already has an account gets the required answer under required and optional, and a value-free 409 under off.

Page-builder form blocks on the identity model already present these outcomes. Turning on required or optional needs an outbound sender; without one the settings save is refused.

Email normalisation and uniqueness, enumeration-safe answers, rate limits, the per-account login backoff, refresh rotation and revocation on password change are automatic. Choose the verification mode, password_min_length and registration Turnstile, and wire forgot-password, reset-password, change-password and sign-out-everywhere as described in Secure authentication for your app (secure-authentication). Never build reset or verification tokens from rules and records, and never list a password field in a public record action.