The everyday flows of `aura.auth`, with the behaviour of each call as observed against a running stack.
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:
| Call | Error |
|---|
| Wrong password | 401, code: 'invalid_credentials', "Identifiants invalides" |
| Invalid or expired link or code | 401, code: 'invalid_otp' |
| Anonymous sign-in when the project disallows it | 403, 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.