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
| Goal | Use |
|---|---|
| Public pages, signed-in forms only | ship 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 site | auth: 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 value | Behavior |
|---|---|
public | Anyone can POST (default for open forms) |
optional | Script runs either way; branch on request auth signed_in |
required | Unsigned visitors redirect to /auth/login first |
Session claims in handlers
After login, *.api.sh can read:
| Field | Meaning |
|---|---|
signed_in | Whether a publish session is present |
sub | Platform identity UUID |
sid | Login username |
name, email | From the login provider when available |
provider | Login provider id (e.g. google, demo) |
role | owner | write | read if a repository member; else omitted / null |
groups | Group 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/logoutin your site — the platform serves them. - Do not put OAuth client secrets or session signing keys in workspace files.
- Do not snapshot
nameonto records when you can storesuband callidentity get.
Failure modes
| What you see | What it means | What to do |
|---|---|---|
| Whole site forces login | Mount policy is 600 or 640 | Public marketing + optional login needs --policy 644 |
| Form accepts anonymous POSTs | Handler auth is public or missing | Set auth: required |
role is null for a signed-in visitor | They are not a repo member | Expected on a public site. Membership is separate from login |
| You built custom OAuth in client JS | Secrets would leak or be unverifiable | Use /auth/login |
whoami in the handler | That is the VFS session, not the visitor | request auth sub |
How to: Publish a site, Put a form on a site. Feature: Publish.