Browse documentation

Route group: /p/:id/auth/*, index is Users. Source: apps/studio/src/features/auth (plus database for policies and the Role Access matrix).

ScreenPathWhat it edits
Usersauth (index)Accounts, via the admin users API
Providersauth/providersauth.methods, auth.social, auth.oauth.custom, auth.phoneOtp
MFAauth/mfaauth.twoFactor
Passkeysauth/passkeysauth.passkey
Organizationsauth/organizationsauth.organization, with the separate project-wide auth.sso switch shown on the same page
Sessionsauth/sessionsLive sessions per user
Hooksauth/hooksauth.hooks.signUp, .accessToken, .send, .events
Rate limitsauth/rate-limitsauth.rateLimits (per-kind limits; the global auth limit is security.rateLimit, under Project Settings → Security)
URL configurationauth/url-configurationauth.urls
Attack protectionauth/protectionauth.attackProtection: CAPTCHA on auth endpoints and on any other route, and breached-password protection
Third-party authauth/third-partyauth.thirdParty
Auditauth/auditBetter Auth audit-log entries
Settingsauth/settingsauth.signup, auth.tokens, auth.sessions, auth.apiKey (user API keys), and the delivery transports email.smtp, email.templates, phone.sms
Policiesauth/policiesPostgres RLS policies
Role Accessauth/accessNothing — read-only matrix of what anon and authenticated can do per table

Every settings screen reads and writes a group through the shared helpers in shared/lib/settings.ts:

GET    /admin/settings/:group
PUT    /admin/settings/:group
DELETE /admin/settings/:group      → revert to the environment fallback
POST   /admin/settings/email/test  { to }
POST   /admin/settings/sms/test    { to }

Saving rebuilds the project's Better Auth instance in place — no redeploy. Field-by-field defaults are in the settings reference; the feature semantics are in Auth.

Users

features/auth/api/users.api.ts:

ActionEndpoint
ListGET /admin/users
DetailGET /admin/users/:id
CreatePOST /admin/users
Promote / demotePOST /admin/users/:id/:action
Reset passwordPOST /admin/users/:id/reset-password { password }
DeleteDELETE /admin/users/:id
Revoke one sessionDELETE /admin/users/:id/sessions/:sessionId

UserDrawer shows a user with their sessions and identities; CreateUserModal creates one.

Note

Through the Console proxy, /admin/users and the credential-adjacent actions require the secrets.manage capability, not just content.write — see the proxy capability map.

Operator sign-in

features/auth/api/session.api.ts handles Studio's own login:

CallPurpose
POST /platform/loginConsole login (returns twoFactor and mustChangePassword)
POST /auth/loginProject-level login when Studio is served by a project API
GET /admin/sessionCurrent admin session
POST /auth/two-factor/verify-totpSecond factor, with trustDevice

PasswordChangeRequired + PasswordChangeForm implement the forced rotation flow described in Console users.

Operator two-factor

features/auth/api/twoFactor.api.ts enrolls the operator's own 2FA:

GET  /auth/get-session
POST /auth/two-factor/enable   { password }   → enrolment payload
POST /auth/two-factor/verify-totp { code }
POST /auth/two-factor/disable  { password }

Enabling and disabling both require the current password.

Providers screen composition

ProviderMethodsPanel (email/password, magic link and email OTP), ProviderSocialPanel (the built-in social list, with AuthProviderLogo), ProviderCustomPanel (generic OIDC/OAuth2) and ProviderPhonePanel (SMS OTP: the auth.phoneOtp behaviour only). The Twilio credentials are the phone.sms transport, shared with SMS second factors, so they live under Settings → Phone / SMS next to SMTP, with the Send test SMS action. Individual credentials render through ProviderField, which shows the __SECRET_SET__ sentinel rather than a stored secret — retyping is only needed when you actually rotate a key.

Attack protection

CAPTCHA (hCaptcha or Cloudflare Turnstile) is one switch, one provider and one secret, applied to two lists:

FieldGuardsEnforced by
Guarded auth endpoints (captchaAuthEndpoints)Any of sign-up, password sign-in, anonymous sign-in, magic-link, email-code and SMS-code requests, and both password recoveries. All of them by default, as in Supabase.Better Auth's captcha plugin
Other guarded routes (captchaRoutes)Any other route, as method + path glob: a table's REST API (/rest/v1/<table>), an edge function (/api/edge/<name>), storage. * matches any run of characters; no method means every method.middlewares/captcha-guard.ts

The editor offers the tables of public ready-made ("guard a table's inserts" adds POST /rest/v1/<table>). Both lists read the token from the same x-captcha-response header and answer the same way: 400 Missing CAPTCHA response without one, 403 Captcha verification failed with a bad one, 503 when the provider cannot be reached. A preflight (OPTIONS) is never guarded. A server-side caller of a guarded route must send a token too.

await client.auth.signUp({ email, password, options: { captchaToken } });
await client.from('orders').insert(order).setHeader('x-captcha-response', captchaToken);
await client.functions.invoke('chat', { body, headers: { 'x-captcha-response': captchaToken } });

Anonymous/guest sign-in is available when auth.methods.anonymous is enabled. It creates a real user with is_anonymous = true; an unauthenticated request remains the separate anon data-plane role and does not create a user.