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. Recipes
  4. React SPA — first integration

Recipes

React SPA — first integration

3 min read·Updated Jun 19, 2026

← All recipes · First time? Set up your app (60 seconds — keys + redirect URI)

A React + Vite single-page app that signs a user in via Authaz Sign-In, gates pages with hooks, and uses <SignInButton> / <SignOutButton> components. Single-tenant, no roles. Pairs with a Hono backend (or any backend that runs the Authaz handler).

1. What you'll build#

A React SPA where / is public, /dashboard requires authentication, and the signed-in user's email appears in the navbar.

2. Application setup#

In the Authaz Dashboard:

  1. New application → name it, choose Single-tenant.
  2. Note the Client ID and Client Secret.
  3. Under Auth Flow Configuration, add http://localhost:5173/auth/callback as an allowed callback URL (Vite's default port).
Previous
Hono — B2B SaaS (multi-tenant)
Next
React SPA — B2B SaaS (multi-tenant)

The React SDK is client-side only — it relies on a backend that runs the Authaz handler at /api/auth. Set up the Hono single-tenant recipe first, or pair with any Authaz-handler-capable backend.

3. Install#

pnpm add react react-dom @authaz/react
pnpm add -D vite @vitejs/plugin-react

4. Configure#

Configure your backend as in the Hono recipe. The React app needs no Authaz env vars — it reads everything via /api/auth/me from the backend.

In vite.config.ts, proxy /api/auth to the backend so cookies flow:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
 
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api/auth": "http://localhost:3000",
    },
  },
});

5. Wire it up#

src/main.tsx:

import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { AuthazProvider } from "@authaz/react";
import App from "./App";
import Dashboard from "./Dashboard";
import Callback from "./Callback";
 
ReactDOM.createRoot(document.getElementById("root")!).render(
  <AuthazProvider basePath="/api/auth">
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<App />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/auth/callback" element={<Callback />} />
      </Routes>
    </BrowserRouter>
  </AuthazProvider>
);

src/Callback.tsx — bridges the OAuth GET callback into the backend's POST handler:

import { useEffect } from "react";
import { useSearchParams } from "react-router-dom";
 
export default function Callback() {
  const [params] = useSearchParams();
  useEffect(() => {
    const form = document.createElement("form");
    form.method = "POST";
    form.action = "/api/auth/callback";
    for (const k of ["code", "state", "error", "error_description"]) {
      const v = params.get(k);
      if (v) {
        const i = document.createElement("input");
        i.type = "hidden"; i.name = k; i.value = v;
        form.appendChild(i);
      }
    }
    document.body.appendChild(form);
    form.submit();
  }, [params]);
  return <p>Completing sign-in…</p>;
}

6. Read the user#

src/App.tsx:

import { useUser, useIsAuthenticated, SignInButton, SignOutButton } from "@authaz/react";
import { Link } from "react-router-dom";
 
export default function App() {
  const user = useUser();
  const isAuthed = useIsAuthenticated();
 
  return (
    <main>
      <h1>Welcome</h1>
      {isAuthed ? (
        <>
          <p>Signed in as {user?.email}</p>
          <Link to="/dashboard">Dashboard</Link>
          <SignOutButton />
        </>
      ) : (
        <SignInButton />
      )}
    </main>
  );
}

7. Protect a route#

Use useRequireUser to redirect unauthenticated visitors automatically. src/Dashboard.tsx:

import { useRequireUser } from "@authaz/react";
 
export default function Dashboard() {
  const user = useRequireUser({ redirectTo: "/" });
  if (!user) return null; // redirect in flight
  return (
    <main>
      <h1>Dashboard</h1>
      <p>Hello, {user.email} (id: <code>{user.id}</code>)</p>
    </main>
  );
}

Or use the declarative <ProtectedRoute> component from @authaz/react if you prefer wrapping route elements.

8. Common gotchas#

  • The Vite proxy is what makes cookies work in dev. Without it, /api/auth/me calls cross-origin and the session cookie is dropped. In production, serve the SPA from the same origin as the backend (or set up a production reverse proxy).
  • AuthazProvider must wrap everything that uses the hooks. Including <BrowserRouter> content. If you see useAuthaz must be used within AuthazProvider, your provider is too low in the tree.
  • The callback route is a page, not an API route. Authaz redirects the browser there with a ?code=...; your client component then POSTs to the backend's /api/auth/callback to complete the exchange.
  • useRequireUser redirects on the next render. Always handle the brief null window before the redirect resolves — don't access user.email until the value is non-null.
  • Don't store the access token in JavaScript. The cookie is HttpOnly by design; if you need to call the backend, do it via the same-origin proxy and the cookie comes along automatically.

9. Next steps#

  • Hono backend recipe — the recommended pairing for a React SPA
  • Multi-tenant React recipe — read tenant from useUser().tenantId
  • Authentication providers — add Google, magic link, passkey