Auth Settings
Each project has its own auth configuration, stored under project.settings.auth
and applied to that project’s Better Auth instance. Changing a setting takes effect
on the next request — the auth service re-fetches the settings and rebuilds the
per-project instance (no restart needed).
Settings
| Field | Default | Effect |
|---|---|---|
email_enabled | true | Allow email/password sign-up & sign-in. When false, Better Auth rejects password auth. |
magic_link_enabled | false | Allow magic-link sign-in (POST …/auth/v1/magiclink). |
email_confirmation_required | false | Require a 6-digit OTP email confirmation before a sign-up yields a session (see below). |
phone_enabled | false | Allow phone / SMS OTP sign-in (POST …/auth/v1/otp with { phone }). Requires a configured SMS provider. |
min_password_length | 8 | Minimum password length (≥ 6) enforced server-side at sign-up. |
session_duration_seconds | 3600 | Access-token / session lifetime (applies to the tokens supabase-js uses). |
refresh_token_duration_seconds | 2592000 | Refresh-token lifetime (30 days) for the SDK tokens. |
providers | [] | Configured OAuth providers. |
sms | null | Configured SMS provider for phone auth (PUT/DELETE …/auth/sms; the auth_token is stored encrypted and masked on read). |
Read
Secrets (OAuth client secrets) come back masked:
curl http://localhost:39001/api/v1/projects/<id>/auth \ -H "Authorization: Bearer $ANVILBASE_TOKEN"Update
PATCH any subset of the general settings (not providers — those use the
providers endpoints):
curl -X PATCH http://localhost:39001/api/v1/projects/<id>/auth \ -H "Authorization: Bearer $ANVILBASE_TOKEN" -H "Content-Type: application/json" \ -d '{ "min_password_length": 12, "session_duration_seconds": 1800, "magic_link_enabled": true }'Email confirmation on sign-up
When email_confirmation_required is enabled, a sign-up does not return a
session. Instead:
-
POST …/auth/v1/signupcreates the user, emails a 6-digit OTP, and returns{ "user": {...}, "session": null }— no access token yet. -
The client collects the code from the user and posts it to verify:
Terminal window curl -X POST "http://localhost:39001/v1/auth/<project_id>/auth/v1/verify" \-H "apikey: $ANON_KEY" -H "Content-Type: application/json" \-d '{"email":"alice@example.com","token":"123456","type":"email"}' -
A valid code returns a full session (
{ user, access_token, token_type, expires_in, expires_at, refresh_token }), confirming the address in the same step.
This is the supabase-js signUp → verifyOtp({ type: 'email' }) flow. The
confirmation email is the project’s email_otp template, sent over the
project’s SMTP config — see Email & Templates. The
same /verify endpoint also powers passwordless Email OTP
sign-in.
How propagation works
The control plane stores these settings (OAuth client secrets and the SMS
provider auth_token encrypted at rest). The auth service fetches the effective
settings — including the decrypted OAuth client secrets and SMS credentials,
over an internal, secret-gated (and, when enabled, mTLS) channel — when it builds
a project’s Better Auth instance. It applies:
session.expiresIn←session_duration_seconds(Better Auth cookie/session)- the facade access-token lifetime (the
expires_in/ JWTexpthatsupabase-jsand@anvilbase/clientuse) ←session_duration_seconds - the facade refresh-token lifetime ←
refresh_token_duration_seconds emailAndPassword.minPasswordLength←min_password_lengthemailAndPassword.enabled←email_enabledsocialProviders← the enabledproviders- the
phoneNumber()plugin’s SMS sender ← the decryptedsmsprovider config (gated byphone_enabledat the facade)
Settings and the built instance are cached per project and invalidated together
when you change a setting (the PATCH/provider endpoints invalidate the project),
so a change takes effect on the next request. The cache is keyed by project id
alone — one project’s settings can never leak into another’s instance.
If the settings fetch fails or the control plane is slow/unreachable, the auth
service degrades to the defaults above rather than stalling sign-in — the fetch
is bounded by a timeout (AUTH_SETTINGS_FETCH_TIMEOUT_MS, default 3000 ms) and a
transient failure is not cached, so the next request re-fetches and picks up the
real settings once the control plane recovers (no manual invalidation needed).
Next: OAuth Providers.