Skip to content

React SPA Quickstart

Ask about this page: Claude ChatGPT Grok

Use this guide when your frontend is a browser-only React app and your backend will validate SigID access tokens separately.

Time: 15-25 minutes after you have a SigID application configuration.

The examples assume an existing Vite + React TypeScript app and a router that serves /auth/callback. The current React SDK requires React 19.2.8 or later. For another bundler, adapt the environment-variable reads. For a static page, use the drop-in script.

What You Will Build

Ask:
  • a public PKCE login flow in React
  • a SigID provider around your app
  • a sign-in button, callback route, session display, and logout button
  • an authenticated fetch call to your backend

New Evaluation: Provision A Sandbox

Ask:

A coding agent can create its own sandbox organization, tenant, application, and ordinary fixture user without a dashboard session. Use the agent quickstart and run:

npx @sigid/cli setup --name my-evaluation --framework react \
  --redirect-uri http://localhost:5173/auth/callback

Map the returned tenant_issuer, client_id, and tenant_id using Integration Settings. The generic env_block is not a complete framework environment file; configure the API audience and scopes separately before testing the protected API. Setup derives allowed browser origins from the exact redirect URI; use your actual preview URL if it differs from the local example. Keep bootstrap management tokens, client secrets and fixture passwords in the private credential file. Configure the app with the public issuer/client ID; let the SDK manage its OAuth session. For unattended execution, supply the keystore passphrase through the CLI's --passphrase-file or --passphrase-stdin option.

Use the returned fixture human for real hosted sign-in, callback, signed-in UI, and logout. The CLI saves its once-only credentials in the owner-only, gitignored .dev/sandbox-fixture.env; keep that file private. Successful metadata probes or expected error responses do not prove hosted login works. Invite or hand off to the human operator after the integration works; activation of the sandbox for production still requires a human owner at AAL2.

For an existing managed workspace, follow the admin configuration path below. init requires an existing tenant and dynamic client registration may require an initial access token; neither replaces sandbox setup. For bootstrap failures, use the agent quickstart troubleshooting with the failing phase, CLI/server versions and sanitized problem/request ID.

Before You Start

Ask:

For an existing managed workspace, in Dashboard choose the tenant, open Applications, click Create Application, and configure a public browser application with:

Field Local example
Allowed Callback URL http://localhost:5173/auth/callback
Allowed Logout URL http://localhost:5173
Allowed Web Origin http://localhost:5173
Allowed Origins (CORS) http://localhost:5173
Client type Public PKCE client
Scopes openid profile email projects:read

If another admin owns the workspace, ask them for the issuer URL, client ID, callback URL, allowed origin, scopes, API audience, and tenant ID from Workspace Admin Quickstart.

Your backend API still needs its own audience, tenant, and scope checks. Do not authorize protected backend data from React state alone.

Install

Ask:
npm install @sigid/client @sigid/react

Configure The Client

Ask:

Create src/sigid.ts:

import { createSigIdClient } from "@sigid/client";

const issuer = import.meta.env.VITE_SIGID_ISSUER_URL?.trim();
const clientId = import.meta.env.VITE_SIGID_CLIENT_ID?.trim();
if (!issuer || !clientId) {
  throw new Error("Set VITE_SIGID_ISSUER_URL and VITE_SIGID_CLIENT_ID, then restart the dev server.");
}

export const sigid = createSigIdClient({
  baseURL: issuer,
  oauth: {
    clientId,
    redirectUri: `${window.location.origin}/auth/callback`,
    scopes: (import.meta.env.VITE_SIGID_SCOPES ?? "openid profile email").split(/\s+/),
  },
});

Create .env.local:

VITE_SIGID_ISSUER_URL=http://auth.sigid.localhost:3000
VITE_SIGID_CLIENT_ID=sigid-react-local
VITE_SIGID_SCOPES="openid profile email projects:read"

Mount The Provider

Ask:
import { SigIdProvider } from "@sigid/react";
import { sigid } from "./sigid";

export function AppRoot({ children }: { children: React.ReactNode }) {
  return <SigIdProvider client={sigid}>{children}</SigIdProvider>;
}

Add Sign-In And Logout

Ask:
import { SignedIn, SignedOut, SignInButton, SignOutButton, useUser } from "@sigid/react";

export function Home() {
  const { user } = useUser();

  return (
    <>
      <SignedOut>
        <SignInButton returnTo="/">Sign in with SigID</SignInButton>
      </SignedOut>
      <SignedIn>
        <p>Signed in as {user?.email ?? user?.id}</p>
        <SignOutButton>Sign out</SignOutButton>
      </SignedIn>
    </>
  );
}

Add A Callback Route

Ask:

Render this component at /auth/callback. Configure your router and hosting fallback so direct navigation to this path loads the React app. The ref guard prevents React Strict Mode's development effect replay from exchanging the same single-use code twice. After success, the return link opens your signed-in home:

import { useEffect, useRef, useState } from "react";
import { useSigId } from "@sigid/react";

export function SigIdCallback() {
  const { handleCallback } = useSigId();
  const [message, setMessage] = useState("Completing sign-in...");

  const started = useRef(false);

  useEffect(() => {
    if (started.current) return;
    started.current = true;
    handleCallback()
      .then(() => setMessage("Sign-in complete."))
      .catch((error) => setMessage(error instanceof Error ? error.message : String(error)));
  }, [handleCallback]);

  return <div><p>{message}</p><a href="/">Return to the app</a></div>;
}

Call Your Backend

Ask:

Use the SDK transport so the backend receives the bearer token:

import { useState } from "react";
import { useSigId } from "@sigid/react";

export function ProjectsButton() {
  const { fetchWithAuth } = useSigId();

  const [message, setMessage] = useState("");

  async function loadProjects() {
    setMessage("Loading...");
    try {
      const response = await fetchWithAuth("/api/projects");
      if (!response.ok) throw new Error(`API failed: ${response.status}`);
      await response.json(); // pass the result to your project-list UI
      setMessage("Projects loaded.");
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Could not load projects.");
    }
  }

  return <div>
    <button onClick={() => void loadProjects()}>Load projects</button>
    <p role="status">{message}</p>
  </div>;
}

/api/projects is an application route you must implement. In Vite development, configure a dev-server proxy for /api to your backend, or use your trusted backend's absolute URL and configure its CORS to allow the frontend origin and Authorization header. Never pass a user-supplied URL to fetchWithAuth, since it attaches credentials. Render loading, success, and failure states in your UI.

Your backend must still validate issuer, audience, tenant, expiry, scope, and subject type. Continue with Backend API Quickstart.

Verify

Ask:

You are successful when:

  • the React app can start hosted login
  • the callback route completes without a state error
  • signed-in UI displays a user or session identifier without displaying tokens
  • logout returns to the signed-out UI
  • backend calls fail closed when the token is missing or invalid

If the backend accepts requests based only on React state, stop and add backend token validation before launch.