Authentication & identity
Auth collections, passwords, OAuth, API tokens, and RBAC roles.
Updated
Authentication is built in. You declare which collections are sign-in capable, and Railbase mounts the endpoints, hashes passwords, issues tokens, and enforces your access rules.
Auth collections
The core guarantees a built-in _users auth collection — end users sign up
and sign in against it with no schema work on your part. Add further auth
collections in the schema when you need separate identity pools:
var Staff = s.AuthCollection("staff") // injects email, password_hash, verified, …
func init() { s.Register(Staff) }
You can have several (_users, staff, customers) — each has its own sign-in
endpoints and an isolated email namespace. Operator/admin accounts are separate
from these app auth collections: seed the first one with
railbase admin bootstrap <email> (a first-run / recovery seed that refuses once
any administrator exists), then onboard the rest through the invite flow (admin UI
→ Administrators → Invite, or POST /api/_admin/admins/invite). See the
_users note in Defining your schema before naming one users.
Endpoints
Each auth collection gets, under /api/collections/{name}/:
POST auth-signup POST auth-with-password
POST auth-refresh POST auth-logout
POST request-verification POST confirm-verification
POST request-password-reset POST confirm-password-reset
POST request-otp POST auth-with-otp
GET auth-with-oauth2/{provider} + /callback
Plus TOTP, WebAuthn, and (where configured) LDAP/SAML. The signed-in identity is
at GET /api/auth/me. Responses carry { token, record }.
OAuth providers
Railbase ships built-in providers — Google, GitHub, Apple, Microsoft plus a
batch of social ones (Discord, GitLab, Bitbucket, …). Each comes online when you
give it credentials — the simplest way is the admin OAuth wizard (Settings →
Auth → Edit auth methods). It writes these oauth.<provider>.* settings, which
you can equally set through the settings API, railbase config set, or the env
fallback below. OAuth config is read at boot, so restart after changing it:
oauth.google.enabled = true
oauth.google.client_id = …
oauth.google.client_secret = …
oauth.google.scopes = openid email profile # space-separated
For 12-factor deployments, each setting has an environment-variable fallback
following the RAILBASE_OAUTH_<PROVIDER>_<FIELD> pattern:
RAILBASE_OAUTH_GOOGLE_CLIENT_ID=…
RAILBASE_OAUTH_GOOGLE_CLIENT_SECRET=…
RAILBASE_OAUTH_GITHUB_CLIENT_ID=…
RAILBASE_OAUTH_GITHUB_CLIENT_SECRET=…
Generic / custom OIDC (Keycloak, Authentik, ZITADEL, Okta, …) has two paths.
The easy one is the wizard's Generic OIDC card (or the oauth.oidc.*
settings): give an issuer URL + client id/secret and Railbase runs OIDC
discovery (<issuer>/.well-known/openid-configuration) to resolve the endpoints
for you:
oauth.oidc.issuer = https://id.example.com # env RAILBASE_OAUTH_OIDC_ISSUER
oauth.oidc.client_id = …
oauth.oidc.client_secret = …
For an IdP without discovery, list provider names in oauth.providers
(env RAILBASE_OAUTH_PROVIDERS, comma-separated) and supply the endpoints
explicitly:
oauth.providers = keycloak
oauth.keycloak.auth_url = https://id.example.com/authorize
oauth.keycloak.token_url = https://id.example.com/token
oauth.keycloak.userinfo_url = https://id.example.com/userinfo
oauth.keycloak.client_id = …
oauth.keycloak.client_secret = …
A provider must be enabled (oauth.<provider>.enabled = true, the default) and
carry a client id + secret for its /auth-with-oauth2/{provider} routes to come
online; a provider with no credentials wired responds 503 not configured.
Apple's client_secret is a short-lived JWT minted with
railbase auth apple-secret.
Clients can discover configured sign-in options with
GET /api/collections/{name}/auth-methods; the response includes password,
OAuth, OTP/magic-link, MFA and WebAuthn availability without exposing secrets.
These are also enforced kill-switches, not just discovery hints: a method
turned off (auth.password.enabled, auth.otp.enabled, auth.totp.enabled,
auth.webauthn.enabled, auth.oauth.<provider>.enabled, …) is refused at the
endpoint with 403 "sign-in method is currently disabled", so disabling one
actually closes it, not merely hides it in the UI.
API tokens
For server-to-server access, mint long-lived bearer tokens:
railbase auth token create --owner <user-uuid> --collection _users \
--name "ci-bot" --ttl 720h [--scopes connector.use,…] [--tenant <uuid>]
railbase auth token list [--owner <uuid>] [--all]
railbase auth token rotate <id> [--ttl 720h]
railbase auth token revoke <id>
--collection is the owner's auth collection and defaults to the built-in
_users — a token minted against a non-existent collection authenticates but
can't load its owner. Two flags narrow a token's reach and are enforced at
runtime: --scopes restricts it to the plugin verbs whose manifest permission
is in the list (everything else on /api/* returns 403 token_scope), and
--tenant pins it to a single workspace. A token never exceeds its owner's own
permissions.
Important
The raw token is shown once, on create/rotate, and can't be recovered. Copy it then; if you lose it, rotate.
Roles & permissions (RBAC)
Authorization is role-based, in three scopes — site (global), tenant
(per-workspace), and plugin (plugin:<slug>, the roles a plugin declares).
Manage roles from the CLI:
railbase role create site editor --desc "Can edit content"
railbase role grant site editor posts.update
railbase role assign _users/<user-id> site editor
railbase role list-for _users/<user-id>
A subject is <auth-collection>/<record-id> — _users/<id> for app users (the
built-in collection), _admins/<id> for operator accounts.
The system_admin site role is immutable. In the plugin model,
plugins declare their own roles in their manifest and register them under the
plugin:<slug> scope at install time; the marketplace catalog marks which of
those roles are billable, which is what makes per-seat billing by role work. See
Licensing & seats.
For enterprise provisioning, SCIM 2.0 is always mounted under /scim/v2/*;
access is gated by possession of a valid SCIM bearer token — create one with
railbase scim token ... — not by a global on/off toggle. Without a token the
endpoints reject every request.