Skip to content

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

FieldDefaultEffect
email_enabledtrueAllow email/password sign-up & sign-in. When false, Better Auth rejects password auth.
magic_link_enabledfalseAllow magic-link sign-in (POST …/auth/v1/magiclink).
email_confirmation_requiredfalseRequire a 6-digit OTP email confirmation before a sign-up yields a session (see below).
phone_enabledfalseAllow phone / SMS OTP sign-in (POST …/auth/v1/otp with { phone }). Requires a configured SMS provider.
min_password_length8Minimum password length (≥ 6) enforced server-side at sign-up.
session_duration_seconds3600Access-token / session lifetime (applies to the tokens supabase-js uses).
refresh_token_duration_seconds2592000Refresh-token lifetime (30 days) for the SDK tokens.
providers[]Configured OAuth providers.
smsnullConfigured 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:

Terminal window
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):

Terminal window
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:

  1. POST …/auth/v1/signup creates the user, emails a 6-digit OTP, and returns { "user": {...}, "session": null } — no access token yet.

  2. 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"}'
  3. 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 signUpverifyOtp({ 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.expiresInsession_duration_seconds (Better Auth cookie/session)
  • the facade access-token lifetime (the expires_in / JWT exp that supabase-js and @anvilbase/client use) ← session_duration_seconds
  • the facade refresh-token lifetimerefresh_token_duration_seconds
  • emailAndPassword.minPasswordLengthmin_password_length
  • emailAndPassword.enabledemail_enabled
  • socialProviders ← the enabled providers
  • the phoneNumber() plugin’s SMS sender ← the decrypted sms provider config (gated by phone_enabled at 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.