Skip to content

@zautha/react

React bindings for Zautha: <ZauthaProvider>, hooks (useAuth, useUser, useSession, …), <Show>, and the prebuilt components from @zautha/ui.

Terminal window
npm i @zautha/react @zautha/ui
import { Show, SignIn, UserButton, ZauthaProvider } from "@zautha/react";
import { ui } from "@zautha/ui";
import "@zautha/ui/styles.css";
export function App() {
return (
<ZauthaProvider publishableKey="pk_test_…" ui={ui}>
<Show when="signed-out">
<SignIn />
</Show>
<Show when="signed-in">
<UserButton />
</Show>
</ZauthaProvider>
);
}

<SignIn /> and <SignUp /> show the providers your instance enabled (Google, GitHub, …) above their forms. The provider sends the user back to ssoCallbackUrl (default: the same page, where the component finishes the sign-in); for a dedicated page, render <AuthenticateWithRedirectCallback /> there and pass its URL:

<SignIn ssoCallbackUrl="/sso-callback" redirectUrl="/dashboard" />;
// app/sso-callback page:
<AuthenticateWithRedirectCallback />;

It finishes the sign-in or sign-up, asks on the same page for whatever is left (the second factor, a missing username, the email code when the provider did not verify the address) and goes on to redirectUrl. A modal sign-in offers providers only with an ssoCallbackUrl. With no target at all (neither the flow’s redirectUrl nor its own redirectUrlComplete prop) it stays on its waiting card once signed in: pass redirectUrlComplete, or go on yourself from onEvent (sign_in.completed, sign_up.completed). It is not the page for connecting an account: <UserProfile /> on its own page finishes a connection itself.

<UserProfile /> is the signed-in user’s account page: name and username, email addresses, connected accounts (connecting a provider leaves for it and comes back to the same page, which finishes it), password, passkeys, two-step verification, active devices and (when the instance allows it) account deletion. Passkeys show only where the browser and the page can use them: on a page under the instance’s passkey domain (production: your domain, shared with the hosted pages; development: the page’s own host, so a passkey added on localhost does not work on the hosted pages and the other way round). See Passkeys in @zautha/js. <UserButton />’s “Manage account” opens it in a modal, or goes to userProfileUrl when you pass one (e.g. your instance’s hosted /user page). Connecting an account from inside that modal does not finish: the provider sends the user back to the app’s page, not into the modal, so nothing completes the connection there — pass userProfileUrl, or render <UserProfile /> on its own page, for connecting accounts.

useAuth() gives the session’s active organization from its token: orgId, orgRole (org:admin), orgSlug and orgPermissions (the role’s custom permissions, org:invoices:read), all null without one. has({ role }) and has({ permission }) take full keys and answer the same as your server does (@zautha/backend, @zautha/nextjs, Zautha.AspNetCore); a system permission (org:sys_…) is never in the token, so it is false. <Show> takes the same conditions. Switch organizations with useZautha()?.setActive({ organization }) (an id, a slug while slugs are on, or null for the personal account); the hooks follow once the new token is in.

<Show when={{ permission: "org:invoices:read" }} fallback={<p>No access</p>}>
<Invoices />
</Show>

<Show> only hides UI: check the same permission on your server.

useReverification(fetcher) wraps an action that may need a recent identity check. When fetcher throws ReverificationRequiredError (e.g. user.updatePassword) or resolves to your backend’s 403 reverification_required response (requireReverification in @zautha/backend, reverificationError in @zautha/nextjs, RequireZauthaReverification in Zautha.AspNetCore), a modal asks the user to verify and the action runs once more. Closing the modal rejects with ReverificationCancelledError.

const { getToken } = useAuth();
const transfer = useReverification(async () =>
fetch("/api/transfer", { method: "POST", headers: { Authorization: `Bearer ${await getToken()}` } }),
);

useAuth().actor (and useSession().session.actor) is { sub, iss } in an impersonation session and null otherwise. With ui on <ZauthaProvider>, the provider shows the impersonation band at the bottom of every page of such a session (“End session” signs out); nothing to add yourself. Without ui (headless use) there is no band, but actor is still set: show your own notice from useAuth().actor.

const { actor } = useAuth();
if (actor !== null) console.log(`support agent ${actor.sub} is signed in as this user`);