These guides are primarily for agent readers. They explain how agents use Sails. If you are a person, start at sails.app or Add Sails.

Add sign-in to a site

Published sites get /auth/login, /auth/callback, and /auth/logout on the same origin as the mount. OAuth client secrets stay on the platform. Do not register custom OAuth apps or put session keys in workspace files.

Done looks like

A visitor clicks Sign in, finishes login, and returns to your page with a sails_session cookie on that host. A form with auth: required redirects unsigned POSTs to login first. request auth sub in the handler prints a platform identity UUID.

Worked example: public site, signed-in form

Job: ship a public demo, protect one POST handler, and read who submitted.

cp -r /templates/static-site-demo ./site
ship create site --policy 644

--policy 644 keeps pages public. Sign-in is only required where you set it.

In a handler (see pages/contacts/create.api.sh in that template):

---
methods: [POST]
auth: required
redirect: /contacts
---
#!/usr/bin/env bash
set -euo pipefail

email="$(request auth email)"
sub="$(request auth sub)"
# … write a page or row keyed by $sub …
respond redirect /contacts

On a page, link to platform routes (same host as the mount):

<a href="/auth/login?return_to=/dashboard">Sign in</a>
<a href="/auth/logout">Sign out</a>

Use /auth/login, not /auth/sign-in or a custom OAuth URL.

Open public_url from ship list. Submit the form signed out — you land on /auth/login, then back on the form flow after login. Signed in, the POST runs and request auth email is set when the provider shared it.

Mount policy vs handler auth

GoalUse
Public pages, signed-in forms onlyship create … --policy 644 + auth: required on those handlers
Members-only app (whole mount)--policy 640 (members) or 600 (owner-only). Anonymous visitors hit /auth/login at the gate
Know who submitted on a public siteauth: required + request auth …

For 640/600 mounts you usually do not need client-side login redirects — the mount gate handles anonymous traffic. Keep auth: required on handlers when the site is public (644) or you want a second check.

auth valueBehavior
publicAnyone can POST (default for open forms)
optionalScript runs either way; branch on request auth signed_in
requiredUnsigned visitors redirect to /auth/login first

Session claims in handlers

After login, *.api.sh can read:

FieldMeaning
signed_inWhether a publish session is present
subPlatform identity UUID
sidLogin username
name, emailFrom the login provider when available
providerLogin provider id (e.g. google, demo)
roleowner | write | read if a repository member; else omitted / null
groupsGroup names in that repository (empty if not a member)

A logged-in platform user who is not a repository member still gets a session (role null, empty groups). Role and groups are mount-scoped to the repository that shipped this site.

security: definer does not change these claims. VFS / sql run as the publisher; request auth is still the visitor. Get the current user with request auth sub (or $SAILS_AUTH_JSON .sub). Do not use Unix id / whoami.

Store only sub on records. Resolve display names at read time:

authors="$(identity get "$id1" "$id2")"
# [{ "id": "…", "name": "Ada Lovelace", "sid": "ada" }, …]

Public fields only: id, name, sid. Never email. Batch up to 64 ids.

What not to do

  • Do not implement /auth/login, /auth/callback, or /auth/logout in your site — the platform serves them.
  • Do not put OAuth client secrets or session signing keys in workspace files.
  • Do not snapshot name onto records when you can store sub and call identity get.

Failure modes

What you seeWhat it meansWhat to do
Whole site forces loginMount policy is 600 or 640Public marketing + optional login needs --policy 644
Form accepts anonymous POSTsHandler auth is public or missingSet auth: required
role is null for a signed-in visitorThey are not a repo memberExpected on a public site. Membership is separate from login
You built custom OAuth in client JSSecrets would leak or be unverifiableUse /auth/login
whoami in the handlerThat is the VFS session, not the visitorrequest auth sub

How to: Publish a site, Put a form on a site. Feature: Publish.