Skip to main content
This guide builds the shape many self-hosted tools have: one static Go binary, one SQLite file, a web UI or API, and a CLI that signs in as the same users. Everything below is implemented by examples/single-binary-sqlite, which also has a smoke test that runs the whole flow. You need Go 1.26 or newer. storage/sqlite pulls in modernc.org/sqlite, which declares Go 1.26. The root theauth-go module itself builds on Go 1.25.

1. Open SQLite and migrate

You own the *sql.DB. Foreign keys must be on, and WAL plus a busy timeout are advised:
Import the driver with _ "modernc.org/sqlite". No cgo is involved, so CGO_ENABLED=0 go build yields a static binary. Details are in Use the SQLite Backend.

2. Configure theauth-go

  • CoreStorage is used because SQLite does not implement organizations, SAML, SCIM or RBAC. Enabling a feature that needs one fails in New with ErrStorageMissingCapability. See Capability interfaces.
  • Bootstrap closes public signup until the first user exists. Without SetupToken, a random one is generated and logged once. a.SetupToken() returns it if you would rather print it yourself.
  • Without RBAC there is no admin role, so supply UserAbilities yourself. It is evaluated on every request, so changing a user’s role narrows their existing tokens at once.
  • Local HTTP only: leave BaseURL as http://... and the cookie is not marked Secure. With an https:// base URL it is.

3. Mount on the stdlib mux

a.Handler() returns an http.Handler serving the canonical /auth/... paths, so no router dependency is needed:
RequireAbility accepts either a session cookie or Authorization: Bearer <token> and returns 403 when the caller lacks the ability. A presented bearer token never falls back to the cookie.

4. First run

The token goes in the X-Setup-Token header or a setupToken body field. After the first user exists, further signups are refused with signup_closed unless OpenSignupAfterFirstUser is set. The first admin must use a password, because magic links cannot carry the token. GET /auth/bootstrap/status returns {"needsSetup": true} until then, which a UI can use to show a setup screen.

5. Mint a token

A signed-in user mints a scoped token with the built-in route. The secret is returned once:
A token cannot exceed the abilities its owner holds now. See API Tokens and Device Login for revocation, listing, service accounts and agent tokens (MintAgentToken).

6. Sign a CLI in with the device grant

On the server, Device enables /auth/device/code, /auth/device/token and /auth/device/approve. You provide the page at VerificationURI: it must sign the user in and post the code to /auth/device/approve. The example ships a 60 line page for this. In the CLI, clientauth needs only the standard library:
DeviceLogin prints the code and URL, polls at the server’s interval, and backs off on slow_down. See CLI Login.

7. Sensitive actions: step-up

Session storage in SQLite supports step-up. Guard dangerous routes so a stale session must re-authenticate:
The client calls POST /auth/step-up with {"method":"password","password":"..."} (or TOTP) first. Passkeys (Config.WebAuthn) and TOTP (Config.TOTP, needs EncryptionKey) are available on SQLite too. See Session management.

8. Housekeeping

Expired rows are not deleted on read. Run store.SweepExpired(ctx, time.Now()) on a ticker. Revocations (a token, a session, an owner) publish on Config.RevocationBus; the default bus is in-process, which is correct for a single binary. Use a.WatchRevocationMiddleware after RequireAbility on SSE or other long-lived handlers so they drop when the credential is revoked.

Run the example

The test boots the server, creates the first admin, mints a token, runs mycli login headlessly and approves it, then calls the API with the device-issued token and logs out.
Last modified on October 7, 2026