Skip to content
BunBase BunBase BunBase Docs Alpha v0.1.0

Authentication

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>
);
}
const { register, loading, error } = useAuth();
await register("user@example.com", "password123");
function ProtectedRoute({ children }: { children: ReactNode }) {
const { isAuthenticated, loading } = useAuth();
if (loading) return <Spinner />;
if (!isAuthenticated) return <Navigate to="/login" />;
return <>{children}</>;
}

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` change
const user = useAuth(s => s.user);
// Re-renders only when `isAuthenticated` changes
const isAuthenticated = useAuth(s => s.isAuthenticated);
// Object selector — shallow comparison, so this won't re-render unless
// user or loading actually changed
const { 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).


FieldTypeDescription
userAuthUser | nullThe authenticated user, or null
loadingbooleanTrue while restoring an existing session with client.auth.me()
isAuthenticatedbooleanTrue 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
errorError | nullLast auth error
clearError() => void

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 render
const 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");
}
});

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]);