Authaz logoAuthaz
DocumentationAPI Reference
  • Get Started

    • Authaz
    • Core Concepts
    • Set up your app
    • Quickstart — cURL
  • Authentication

    • Authentication Settings
    • Signup
    • Invitations
    • Password Authentication
    • Multi-Factor Auth
    • Magic Link
    • OAuth / Social Login
    • Passkey (WebAuthn)
    • SAML SSO
    • Machine-to-Machine (M2M)
    • API Keys
  • Authorization

    • Authorization
    • Resources
    • Policies
    • Roles
    • Access Explorer
  • Tenancy

    • Multi-tenancy
    • Tenancy Customization
  • Brand & Host

    • Branding
    • Custom Domains
    • Communications & Email Templates
  • Operate

    • Users
    • Analytics
    • Audit Logs
    • Application Settings
  • SDK Quickstarts

    • Quickstart — Next.js
    • Quickstart — React SPA
    • Quickstart — Hono
    • Quickstart — .NET (Authaz.Sdk)
  • Recipes

    • Recipes & Cookbook
    • Next.js — first integration
    • Next.js — B2B SaaS (multi-tenant)
    • Hono — first integration
    • Hono — B2B SaaS (multi-tenant)
    • React SPA — first integration
    • React SPA — B2B SaaS (multi-tenant)
    • .NET — first integration
    • .NET — B2B SaaS (multi-tenant)
  • Reference

    • Tokens
    • API Reference
    • Errors & Troubleshooting
  • Documentation

    • How Authaz is Built
  1. Authaz
  2. Docs
  3. SDK Quickstarts
  4. Quickstart — React SPA

SDK Quickstarts

Quickstart — React SPA

4 min read·Updated Jun 19, 2026

cURL · Next.js · React · Hono · .NET

@authaz/react is the client-side piece. It gives you AuthazProvider, hooks (useAuthaz, useUser, useLogin, useLogout), and route guards (ProtectedRoute, useRequireAuth).

It does not talk directly to Authaz — instead it talks to your backend, which mounts the OAuth handler. For a Hono backend that's @authaz/hono. For Express/Fastify/etc., proxy the same routes to the OAuth flow yourself, or run a tiny Hono app alongside your existing backend.

This quickstart assumes you have a backend at /api/auth/* already — the sets that up in about 8 minutes. The frontend code is the same regardless of backend. About 5 minutes for the frontend half on its own.

Previous
Quickstart — Next.js
Next
Quickstart — Hono
Hono quickstart

Prerequisites#

  • A React 18+ app (Vite, CRA, TanStack Start — any setup works).
  • A backend serving the auth routes at /api/auth/login, /api/auth/callback, /api/auth/logout, /api/auth/me, /api/auth/refresh. The Hono quickstart covers this end-to-end.
  • An Authaz application with http://localhost:3000/auth/callback registered. New to Authaz? Set up your app in 60 seconds — the values it gives you go on the backend, not here.

Step 1 — Install#

npm install @authaz/react
# pnpm: pnpm add @authaz/react
# yarn: yarn add @authaz/react
# bun:  bun add @authaz/react

Step 2 — Wrap the app in AuthazProvider#

// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { AuthazProvider } from "@authaz/react";
import App from "./App";
 
ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <AuthazProvider basePath="/api/auth" autoRefresh>
      <App />
    </AuthazProvider>
  </React.StrictMode>
);

basePath must match where your backend mounted the auth handler. autoRefresh retries /me once after a silent token refresh on 401.

Step 3 — Login / logout buttons#

// src/components/Navbar.tsx
import { useAuthaz } from "@authaz/react";
 
export const Navbar = () => {
  const { isAuthenticated, isLoading, user, login, logout } = useAuthaz();
 
  if (isLoading) return <nav>…</nav>;
 
  return (
    <nav>
      {isAuthenticated ? (
        <>
          <span>{user?.email}</span>
          <button onClick={() => logout()}>Logout</button>
        </>
      ) : (
        <button onClick={() => login()}>Login</button>
      )}
    </nav>
  );
};

login() redirects to your backend's /api/auth/login, which redirects to Authaz Sign-In. After authentication, the user lands back on your SPA with a session cookie.

Step 4 — Protect a route#

Two options. Pick whichever fits your router:

With a router-agnostic guard component:

import { ProtectedRoute } from "@authaz/react";
 
<ProtectedRoute fallback={<p>Loading…</p>}>
  <Dashboard />
</ProtectedRoute>

If the user isn't signed in, ProtectedRoute calls login() automatically (redirect to Authaz Sign-In). You can override by passing onUnauthenticated.

With a hook inside the page component:

import { useRequireAuth } from "@authaz/react";
 
export const Dashboard = () => {
  useRequireAuth();
  return <h1>Dashboard</h1>;
};

For pages that should only show to anonymous users (e.g. the marketing landing page), use <GuestRoute> — it kicks signed-in users to /dashboard (or wherever you point it).

Step 5 — Read the user#

useUser() returns null while loading or unauthenticated, and the user object once signed in:

import { useUser } from "@authaz/react";
 
export const Profile = () => {
  const user = useUser();
  if (!user) return null;
 
  return (
    <div>
      <h2>{user.name ?? user.email}</h2>
      <p>MFA: {user.mfaEnabled ? "on" : "off"}</p>
      {user.organizations.map((org) => (
        <p key={org.organizationId}>
          {org.organizationId} — {org.accessLevel}
        </p>
      ))}
    </div>
  );
};

The full user shape:

type AuthazUser = {
  id: string;
  email: string;
  name: string;
  mfaEnabled: boolean;
  organizations: { organizationId: string; accessLevel: string }[];
  tenantId?: string;
  organizationId?: string;
};

Hook reference#

HookUse when
useAuthaz()You need everything: user, state, login, logout, refresh.
useUser()Just the user object. Returns null until loaded.
useIsAuthenticated()Boolean — render-or-not decisions.
useIsLoading()True while the initial /me call is in flight.
useLogin() / useLogout()Stable callbacks — handy in event handlers and effects.
useRequireAuth({ redirectTo? })Ensure the user is signed in for this page.
useRequireUser({ redirectTo? })Same, but returns the user object once it resolves.

Calling Authaz from the frontend#

Don't. Authaz tokens (access tokens, API keys) belong on the server. Make Authaz calls from your backend, expose narrowed responses to your SPA, and trust the user's session cookie for identity.

The one exception is /api/auth/me, which the SDK already handles — your frontend never sees the access token directly.

What success looks like#

With the backend running and the app up, hitting your dev URL should:

  1. Show the Login button (because useAuthaz() returns isAuthenticated: false).
  2. Click Login → redirect to your backend /api/auth/login → Authaz Sign-In → sign up → land back on the SPA.
  3. The button now shows your email next to a Logout button.

useUser() returns this once signed in:

{
  id: "user_01h...",
  email: "you@example.com",
  name: "Your Name",
  mfaEnabled: false,
  organizations: [{ organizationId: "0199...", accessLevel: "owner" }],
}

If you see your email render in the navbar, auth is working.

If something's wrong#

SymptomLikely causeFix
Login button stays after sign-in; useAuthaz() returns isAuthenticated: falseBackend isn't reachable on the same origin — /api/auth/me is 404 or 401.Check the backend is running. If frontend and backend are on different ports in dev, proxy /api (Vite snippet at the bottom of the Hono quickstart).
CORS error in console on /api/auth/meFrontend and backend on different origins, no CORS config.Either same-origin via dev proxy, or set CORS on the backend with credentials: "include" allowed.
useAuthaz() stuck on isLoading: truebasePath doesn't match where the backend mounted the handler.The basePath prop on AuthazProvider must match the backend's mount path (default /api/auth).
login() redirects to a 404Backend isn't serving GET /api/auth/login.Confirm the Authaz handler is mounted at basePath.
Logged in, but reloading the page logs you outSession cookie isn't being sent — likely a same-site or cross-origin issue.In dev, prefer same-origin via proxy. In production, host frontend and backend under the same registrable domain.

For everything else, the errors catalog covers every code Authaz returns.

Complete worked example#

  • React — first integration (single-tenant) — the full SPA + Hono backend, end to end.
  • React — B2B SaaS (multi-tenant) — same, with tenant pickers and tenant-scoped tokens.

Next steps#

  • Hono quickstart — the backend half if you don't have one yet.
  • Next.js quickstart — same hooks, with the auth handler colocated in the app.
  • Multi-tenancy — handling the tenant picker after login.