Authentication
useAuth
Section titled “useAuth”Reactive authentication state. Works with the session stored in the underlying BunBaseClient — call client.auth.restoreSession before mounting the provider to avoid a flash of unauthenticated UI.
import { useAuth } from "@bunbase/react-sdk";
function Header() { const { user, isAuthenticated, logout } = useAuth();
return ( <header> {isAuthenticated ? ( <> <span>{user?.email}</span> <button onClick={logout}>Log out</button> </> ) : ( <a href="/login">Log in</a> )} </header> );}import { useBunBase, useAuth } from "@bunbase/react-sdk";
function LoginForm() { const { client } = useBunBase(); const { login, loading, error, clearError } = useAuth();
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); const form = new FormData(e.currentTarget); const result = await login(form.get("email") as string, form.get("password") as string); if ("totp_required" in result) { // Render your own 2FA step, then complete sign-in with verifyTotp(). await client.auth.verifyTotp(result.totp_token, codeFromAuthenticatorApp); } // On success, user and isAuthenticated update automatically. }
return ( <form onSubmit={handleSubmit}> <input name="email" type="email" /> <input name="password" type="password" /> <button disabled={loading}>Log in</button> {error && <p onClick={clearError}>{error.message}</p>} </form> );}Register
Section titled “Register”const { register, loading, error } = useAuth();
await register("user@example.com", "password123");Protected routes
Section titled “Protected routes”function ProtectedRoute({ children }: { children: ReactNode }) { const { isAuthenticated, loading } = useAuth();
if (loading) return <Spinner />; if (!isAuthenticated) return <Navigate to="/login" />; return <>{children}</>;}Selector (prevent extra re-renders)
Section titled “Selector (prevent extra re-renders)”Pass a selector to useAuth to subscribe only to the slice of state your component needs. The component re-renders only when the selected value changes (shallow equality).
// Re-renders only when `user` changes — not when `loading` or `error` changeconst user = useAuth(s => s.user);
// Re-renders only when `isAuthenticated` changesconst isAuthenticated = useAuth(s => s.isAuthenticated);
// Object selector — shallow comparison, so this won't re-render unless// user or loading actually changedconst { user, loading } = useAuth(s => ({ user: s.user, loading: s.loading }));Without a selector, useAuth() re-renders whenever any field changes (same as before — use this when you need multiple fields and don’t want to split the call).
Return value
Section titled “Return value”| Field | Type | Description |
|---|---|---|
user | AuthUser | null | The authenticated user, or null |
loading | boolean | True while restoring an existing session with client.auth.me() |
isAuthenticated | boolean | True once the current user has been loaded |
login | (email, password) => Promise<LoginResult> | Returns either a full session or a TOTP challenge when 2FA is enabled |
register | (email, password) => Promise<void> | Creates the account and lets the auth listener update user |
logout | () => Promise<void> | Clears tokens and sets user to null |
error | Error | null | Last auth error |
clearError | () => void |
Token persistence
Section titled “Token persistence”Tokens are stored in-memory by the SDK. Persist them in BunBaseProvider setup:
const at = localStorage.getItem("bb_at");const rt = localStorage.getItem("bb_rt");if (at && rt) client.auth.restoreSession(at, rt);
client.auth.onAuthChange((session) => { if (session) { localStorage.setItem("bb_at", session.accessToken); localStorage.setItem("bb_rt", session.refreshToken); } else { localStorage.removeItem("bb_at"); localStorage.removeItem("bb_rt"); }});For React Native with MMKV (synchronous), restore the session before the component tree mounts so useAuth() initializes with the user already set — no loading state, no flicker:
// lib/client.ts — runs at import time, before any React renderconst at = storage.getString("bb_at");const rt = storage.getString("bb_rt");const user = storage.getString("bb_user");if (at && rt) { client.auth.restoreSession(at, rt, user ? JSON.parse(user) : undefined);}
client.auth.onAuthChange((session) => { if (session) { storage.set("bb_at", session.accessToken); storage.set("bb_rt", session.refreshToken); const u = client.auth.getCachedUser(); if (u) storage.set("bb_user", JSON.stringify(u)); } else { storage.delete("bb_at"); storage.delete("bb_rt"); storage.delete("bb_user"); }});Session revocation
Section titled “Session revocation”When an admin revokes a session or the user’s password changes, the server pushes an auth event over the WebSocket. The SDK handles it automatically — useAuth().user becomes null and useAuth().isAuthenticated becomes false with no extra code required.
function App() { const { isAuthenticated } = useAuth();
useEffect(() => { if (!isAuthenticated) router.replace("/login"); }, [isAuthenticated]);}This works instantly when the realtime connection is active. If your app has no realtime connection, use startSessionWatch() as a fallback — call it once after the user logs in and stop it on logout:
const { client } = useBunBase();useEffect(() => { return client.auth.startSessionWatch(30_000); // returns cleanup fn}, [client]);