Browser authentication example
A minimal, same-origin fetch application using Sigil's public HTTP API. It
covers registration, explicit email verification, login, password reset, logout
and revocation of all sessions. No SDK build or real email credentials are needed.
Run from a clean checkout
Requirements: the Go version in go.mod, Docker for the disposable PostgreSQL
server, and an available loopback port 8081. Run from the repository root:
make pg-up
make browser
export SIGIL_DATABASE_URL='postgres://auth_ci:auth_ci@127.0.0.1:55432/auth_ci?sslmode=disable'
./bin/browser --migrate
If the sigil-ci container is already running, skip make pg-up. Open
http://127.0.0.1:8081/ (use this exact host, not localhost). Register, select
Check mail, open the verification link, select Confirm email, then sign
in. Request a reset, check mail again, open the link and submit a new password.
The reset signs out every browser; log in explicitly with the new password.
Log out revokes the current session; Revoke all sessions also invalidates
other browsers' sessions. Buttons report safe API errors and rate-limit hints.
--migrate explicitly creates the isolated sigil_browser schema and applies core
migrations. Later starts can omit it; startup checks schema compatibility without
DDL. The host owns the listener, pool, delivery scheduling and graceful shutdown.
Ctrl-C drains HTTP requests, closes Auth, then closes the borrowed pool.
The first start writes a random, mode-0600 key to ignored bin/browser.key.
Keep it with the database across restarts: it supplies the deployment secret and
a separately derived mail encryption key. Losing it makes pending mail unreadable;
make clean removes it. This is a disposable local example, not a key-management
recipe. Flags: --addr (numeric loopback only), --database, --schema, --key-file
(parent directory must exist), and --migrate.
Mail and browser boundaries
Registration/reset enqueue encrypted mail transactionally in PostgreSQL. Check
mail schedules up to 20 dispatches and drains the bounded LocalMailSink into
the page. There is no background sender or external provider. Previewed mail is
held only in the page; reload discards the inbox. Pending undelivered mail survives
a restart with the same database/key; already dispatched previews do not.
The preview contains real development proofs and is intentionally local and
shared by everyone using this example process. Never expose it through a proxy
or tunnel. Binding accepts only numeric loopback addresses; all host headers must
match the listening address. Preview POST requires the exact Origin and JSON
content type, has no cross-origin CORS grant, and is never cached. The page uses
external scripts, a restrictive CSP and text-only rendering of email metadata.
Mail proofs live in URL fragments, are removed from the address bar on load, and
are submitted only after an explicit button click. GET never consumes a proof.
Passwords and tokens are not stored in browser storage or logged. Password fields
are cleared after submissions. Every mutation obtains fresh CSRF proof bound to
the browser's current HttpOnly cookie. Fetch always sends same-origin credentials.
Loopback development cookies are HttpOnly, host-only, SameSite=Lax and non-Secure;
the production HTTPS policy remains covered by the library's integration tests.
Automated browser journey
Also requires Node/npm versions in package.json. From the root:
npm ci --ignore-scripts --no-audit --no-fund
npx playwright install chromium
make check-browser
On Linux, use npx playwright install --with-deps chromium to install required
system libraries. The test command builds the server and launches Chromium on
port 8082 against PostgreSQL (the DSN above is the default). It creates a fresh
random test schema and removes only that schema at teardown via the existing Go
PostgreSQL driver. Test retries are disabled; real abuse policies stay enabled.
An interrupted process may leave a sigil_browser_test_<32 hex digits> schema;
go run ./examples/browser/testdb <schema> removes that isolated test schema with
SIGIL_DATABASE_URL set. It refuses the interactive schema and arbitrary names;
it also accepts the sigil_client_test_<32 hex digits> schemas of the
client journey example.
Four browser tests cover authentication beyond the startup timeout, the complete
journey, unverified login rejection, scanner-safe links, session persistence
through reload, HttpOnly/SameSite/path
cookie policy, missing-CSRF rejection, reset replay, rejection of the old
password, revocation of other browser sessions, logout cookie clearing and replay
of the logged-out cookie, plus host/origin rejection on mail preview. No trace,
video or screenshot artifacts containing credentials are retained. The PostgreSQL
CI job runs the same command. Go tests cover the local host/key boundaries.
Vertical-slice findings and effort
The step-5 checkpoint is recorded in browser-vertical-slice.md.
This example is intentionally limited to local HTTP and Chromium. HTTPS cookie
contracts have Go integration coverage; Firefox/WebKit, production delivery,
account settings and frontend bindings belong to the linked follow-up tasks.