Aurabase Logo
aurabasedocs
docs›Guides›Authenticate users (SDK)

Authenticate users with the JavaScript SDK

The everyday flows of `aura.auth`, with the behaviour of each call as observed against a running stack.

11 min read·Level intermediate·Revised on Oct 12, 2026
This English text was generated automatically from the French original and has not been reviewed yet.
#
Basics

1. Sign up and sign in

auth.tstypescript
import { createClient, isMfaChallenge } from '@aurabase/aurabase-js'

const aura = createClient(process.env.AURA_URL!, process.env.AURA_ANON_KEY!)

// Create an account
const { data, error } = await aura.auth.signUp({ email: 'ada@example.com', password: 'a-long-passphrase' })

// Sign in
const login = await aura.auth.signInWithPassword({ email: 'ada@example.com', password: 'a-long-passphrase' })
if (login.error) return showError(login.error.message)
if (login.data && isMfaChallenge(login.data)) return askForTotpCode(login.data.mfa_token)
// otherwise a session now exists

Both calls return the same shape. On success, data holds access_token, refresh_token, expires_in (900 seconds) and the user, and the client stores the session for you. The next request is already authenticated.

What signUp does depends on your project settings. In our test project it signed the user in at once with email_verified: false, and a project can require confirmation first.

Failures you should handle, as observed:

CallError
Wrong password401, code: 'invalid_credentials', "Identifiants invalides"
Invalid or expired link or code401, code: 'invalid_otp'
Anonymous sign-in when the project disallows it403, FORBIDDEN

signInAnonymously() creates a user without an e-mail address, but in a new project it is disabled and returns 403 until you enable it in the project's auth settings.

#
State

2. The session and its events

session.tstypescript
const { data: { session } } = await aura.auth.getSession() // no network call: reads the store
const { data: user } = await aura.auth.getUser()           // asks the server

const { data: { subscription } } = aura.auth.onAuthStateChange(({ event, user }) => {
  // event: 'INITIAL_SESSION' | 'SIGNED_IN' | 'TOKEN_REFRESHED' | 'USER_UPDATED' | 'SIGNED_OUT' | 'MFA_CHALLENGE'
})
subscription.unsubscribe()

A real run produced this sequence: INITIAL_SESSION, SIGNED_IN (sign-up), SIGNED_IN (sign-in), USER_UPDATED, TOKEN_REFRESHED, SIGNED_OUT.

  • The client refreshes the access token before it expires. Call aura.auth.refreshSession() to force it.
  • getSession() is local and fast: use it for rendering. Use getUser() when you need the server's current view of the user.
  • To hand a session to the client from elsewhere (a cookie, a server), use setSession(accessToken, refreshToken). An invalid pair is refused and the previous session is kept.
  • For server-side rendering, see Next.js App Router and SSR.
#
Account

3. Profile and password

profile.tstypescript
// Metadata: pass it in `data`
await aura.auth.updateUser({ data: { theme: 'dark', plan: 'free' } })
// → user.user_metadata is { plan: 'free', theme: 'dark' }, and getUser() returns the same

// Change the e-mail: the address does NOT change yet
await aura.auth.updateUser({ email: 'new@example.com' })
// A confirmation link goes to the new address. Until it is used, user.email keeps the old value.
// Confirm with: aura.auth.verifyOtp({ type: 'email_change', token })

// Change the password
await aura.auth.updatePassword('a-new-long-passphrase', 'a-long-passphrase')
Changing the password signs you out

The server revokes all sessions when the password changes. updatePassword clears the local session and emits SIGNED_OUT: sign in again with the new password. In a real run, getSession() was empty right after the call, and signing in with the new password worked.

Forgot password: aura.auth.resetPasswordForEmail({ email }) sends the reset e-mail. The user then completes it with verifyOtp({ type: 'recovery', token, password }).

Delete the account: aura.auth.deleteUser(password) asks for the current password, deletes the account and clears the local session.

#
Alternatives

4. Links, codes and OAuth

passwordless.tstypescript
// Magic link by e-mail
await aura.auth.signInWithMagicLink({ email })
// → a generic confirmation message
// The answer is the same whether or not the account exists: it does not leak accounts.

// One-time codes
await aura.auth.signInWithEmailOtp(email)
await aura.auth.signInWithSmsOtp(phone)
await aura.auth.verifyOtp({ type: 'email', email, code })     // also: 'sms', 'magiclink', 'signup', 'recovery', 'email_change'

// OAuth: the provider is the FIRST argument, a string
const { data } = await aura.auth.signInWithOAuth('google', { redirectTo: 'https://app.example.com/callback' })
window.location.href = data!.url

signInWithOAuth returns the URL to open; it does not navigate by itself. The provider must be enabled and configured in the project, otherwise the start URL answers PROVIDER_NOT_CONFIGURED.

Not the supabase-js form

signInWithOAuth({ provider: 'google' }) returns the error invalid_provider. Pass the provider as the first argument.

After the redirect back, the client reads the session from the URL by itself (detectSessionInUrl). For the PKCE flow, pass flowType: 'pkce', keep the default persistent store, and the client exchanges the code for you.

Link a second provider to a signed-in account with linkIdentity(provider), list them with listIdentities() and remove one with unlinkIdentity(identityId).

#
Second factor

5. Multi-factor authentication

mfa.tstypescript
// 1. Enrol: show the QR code built from otpauth_url
const { data: enrol } = await aura.auth.mfaEnroll()   // → { secret, otpauth_url }
// 2. Confirm with the first code from the authenticator app: you receive recovery codes
const { data: confirm } = await aura.auth.mfaVerifySetup(codeFromApp) // → { recovery_codes: [...] }

// 3. From now on, signing in returns a challenge instead of a session
const login = await aura.auth.signInWithPassword({ email, password })
if (login.data && isMfaChallenge(login.data)) {
  await aura.auth.mfaVerify({ mfa_token: login.data.mfa_token, code: codeFromApp })
}

mfaUnenroll(password) turns it off, and regenerateRecoveryCodes(code) issues new recovery codes. A challenge emits MFA_CHALLENGE and no session is stored until mfaVerify succeeds.

#
Devices

6. Devices and sessions

devices.tstypescript
const { data } = await aura.auth.listSessions()
// [{ id, device_info, ip_address, created_at, expires_at, last_used_at, is_current }, …]

await aura.auth.revokeSession(sessionId)   // sign out one device
await aura.auth.revokeAllSessions()        // sign out everywhere, including this one
await aura.auth.signOut()                  // this device only

is_current marks the session you are using. signOut revokes the refresh token on the server when there is one, clears the local session and emits SIGNED_OUT.

#
Server

7. Administer users

User administration needs a service_role key. It is available only on the server: reading aura.authAdmin in a browser throws an error.

server/users.tstypescript
import { createAdminClient } from '@aurabase/aurabase-js'

const admin = createAdminClient(process.env.AURA_URL!, process.env.AURA_SERVICE_ROLE_KEY!)

const { data: users } = await admin.authAdmin.listUsers({ limit: 20, search: 'ada' })
const { data: created } = await admin.authAdmin.createUser({ email: 'bob@example.com', password: 'a-long-passphrase', email_verified: true })
await admin.authAdmin.banUser(created!.id)
await admin.authAdmin.unbanUser(created!.id)
const { data: link } = await admin.authAdmin.generateLink(created!.id, 'magic') // → { link, token }
await admin.authAdmin.deleteUser(created!.id)

const { data: stats } = await admin.authAdmin.getUserStats() // → { total, verified, banned, mfa_enabled, new_7d }

generateLink accepts 'magic', 'recovery' or 'invite'. The admin client also manages the project's auth settings (getSettings, updateSettings), e-mail templates, sessions of every user and the audit log (getAuditLog). Every method is listed in the auth reference.

Last updated · Oct 12, 2026