Docs

Let your app's admins manage users (auth.users)

An admin screen inside your own app: people with a role you choose add, list, re-role, disable and remove your app's users with their own sign-in, no server key. Every rule is checked by the server.

Updated Nov 4, 2026

Many apps have their own admins: the manager of a CRM adds a new sales rep, a school admin adds teachers. auth.users lets them do that from inside your app, with their own session. You choose which app roles may do it; nobody else can, and no server key ever goes into the browser.

Your appadmin signed in(their ownsession)Gatewaykeys and visitorsrefusedSovaria Authroles read fromthe database;every rule checkedYour app's userscreated, re-roled,disabled, removed

#Turn it on

  1. Have the roles

    Your app's roles are in Authentication › Roles (for example ADMIN, SALES). Give at least one person the admin role (console Users page, or auth.admin).

  2. Choose who manages users

    In Authentication › Users, under Who can manage users in your app, tick the roles (for example ADMIN) and save. It is off until you do (no roles). The setting is userAdminRoles in the project settings; only the project team can change it.

  3. Build the screen

    Call auth.users from your app. Sovariq can do all of this for you: ask for "an admin page where admins add staff".

#Use it

// Show the screen only to people the server says can use it.
const { can_manage_users, assignable_roles } = await sovaria.auth.users.access()

const { users, nextCursor } = await sovaria.auth.users.list({ search: 'ada', status: 'active', limit: 50 })

// Every user, 100 per request following the cursor (stops after max, default 10,000)
for await (const u of sovaria.auth.users.all({ role: 'SALES' })) console.log(u.email)

// Invite by email (a sign-in link), or make a temporary password shown once:
const { user, temporary_password } = await sovaria.auth.users.create({
  email: 'rep@acme.com', name: 'New Rep', roles: ['SALES'], mode: 'temporary_password',
})

await sovaria.auth.users.setRoles(user.id, ['SALES', 'MANAGER'])
await sovaria.auth.users.disable(user.id)     // signed out everywhere, can't sign in
await sovaria.auth.users.enable(user.id)
await sovaria.auth.users.delete(user.id)
CallHTTPNotes
access()GET /api/v1/auth/app-users/accessAny signed-in user may ask. can_manage_users, your_roles, user_admin_roles, assignable_roles.
list({ limit, cursor, search, status, role })GET /api/v1/auth/app-usersNewest first, at most 100 a page. Id, email, name, roles, active, email verified, must_change_password, created, last sign-in. Never passwords, factors or sessions.
create({ email, name, roles, mode })POST /api/v1/auth/app-usersmode: 'invite' (default) emails a sign-in link. 'temporary_password' returns temporary_password once.
setRoles(id, roles)PUT /api/v1/auth/app-users/{id}/rolesReplaces the roles. The user stays signed in: their requests use the new roles at once (within 20 seconds at most), even with a token issued before the change, and the next refreshed token carries them.
disable(id) / enable(id)POST …/{id}/disable · …/enableDisabled users are signed out everywhere and can't sign in.
delete(id)DELETE /api/v1/auth/app-users/{id}Deletes the user and everything tied to the account.

#Temporary passwords

With mode: 'temporary_password' the server makes a strong password that meets your password rules and returns it once, only to the admin who made the account. Show it on screen so they can hand it over; never store it, log it or send it to analytics. The account is marked must_change_password (user.app_metadata.must_change_password, and must_change_password on /auth/me) until the user sets their own password with updateUser({ password, currentPassword }) or a password reset. If email verification is on, the user also confirms their email the first time.

The server enforces it. Until the password is changed, that user's session gets 403 PASSWORD_CHANGE_REQUIRED on every data call: tables (REST, GraphQL), storage, functions, realtime, agents, and managing users. Only these still work: reading and updating their own account (GET/PATCH /auth/me, auth.updateUser), the confirmation code for the change (auth.reauthenticate), signing out, refreshing and the password reset. A user an admin never gave a temporary password is not affected.

// At sign-in: send the user to a "choose a new password" screen first.
const me = await sovaria.auth.me()
if (me.must_change_password) {
  const { error } = await sovaria.auth.updateUser({ password: newPassword, currentPassword: temporaryPassword })
  if (!error) await sovaria.auth.refreshSession()   // the refreshed session no longer carries the flag
}

Refresh right after the change: the access token used for the change still carries the flag until it is refreshed or expires. The starter app does all of this for you: its PasswordChangeGate shows the screen and changePassword refreshes the session. If your own server verifies Sovaria tokens with the project's signing keys, check app_metadata.must_change_password in the claims as well.

#What the server enforces

Nothing here trusts your app's code. A user who edits the page in their browser, or sends requests by hand, gets the same answers as your UI:

RuleRefused with
The caller is a signed-in app user of this project with their own session: the anon key, server keys, anonymous visitors and impersonation sessions are refused.403 APP_USER_SESSION_REQUIRED
The feature is on (at least one role chosen).403 USER_ADMIN_OFF
The caller holds one of the chosen roles, read from the database (the roles in a token or request don't count).403 USER_ADMIN_ROLE_REQUIRED
Only your app's own roles can be given (Authentication › Roles); never team/organization roles or reserved names.422 UNKNOWN_APP_ROLE · RESERVED_APP_ROLE
A role from the "manage users" list can only be given by someone who has that role.403 ROLE_ESCALATION
Nobody changes, disables or deletes their own account here.403 SELF_CHANGE
A user holding a "manage users" role the caller doesn't have can't be changed by them. With several roles in the list there is no ranking: give your top admins every listed role.403 TARGET_OUTRANKS
The last active user admin can't be removed, disabled or demoted.409 LAST_USER_ADMIN
Only users of the same project (and environment) exist here.404 USER_NOT_FOUND
New users per hour per project, and changes per hour per admin (Auth › Settings › Rate limits: appUserCreatesPerHour, default 50; appUserChangesPerHour, default 200). Invites also count as auth emails.429 AUTH_RATE_LIMITED
An email that already has an account in this app.409 EMAIL_EXISTS
A user (admin or not) still on a temporary password can only change it: everything else waits until they do.403 PASSWORD_CHANGE_REQUIRED
  • Every change is recorded on both accounts' security activity (auth.app_admin.* for the admin, admin.app_user.* for the user) and in your project's audit log.
  • The user gets a security email when their roles change, their account is disabled or turned back on, or it is deleted (unless you turned security notifications off).
  • Row security is unchanged: managing users gives no extra access to your tables. Rules like auth.has_role('ADMIN') keep deciding which rows anyone sees.
  • Your team keeps the console and auth.admin (server code) for everything, including people the in-app admins can't touch.