---
summary: Step-by-step React SPA quickstart for SigID hosted login, callback handling, session display, logout, and backend token handoff.
tags:
  - developers
  - quickstart
  - react
  - spa
categories:
  - For Developers
---

# React SPA Quickstart

<!-- agent:page
You are a coding agent integrating SigID hosted login into a browser-only React SPA by following this guide end to end.
First collect from the user or workspace admin: issuer URL, client ID, allowed callback URL (http://localhost:5173/auth/callback locally), allowed logout/web-origin/CORS values, scopes (e.g. openid profile email projects:read), API audience, and tenant ID. The Dashboard application must be a public PKCE client.
Implementation order:
- npm install @sigid/client @sigid/react
- create src/sigid.ts with createSigIdClient and .env.local with VITE_SIGID_ISSUER_URL, VITE_SIGID_CLIENT_ID, VITE_SIGID_SCOPES
- wrap the app in SigIdProvider, add SignInButton/SignOutButton UI with useUser, and render a callback component at /auth/callback that calls handleCallback()
- call the backend with fetchWithAuth from useSigId so the bearer token is attached
Adapt file paths and env handling to the project's bundler and router; do not replace existing auth wiring without confirming with the user.
Verify: the app can start hosted login, the callback completes without a state error, signed-in UI shows a user without showing tokens, logout returns to the signed-out UI, and backend calls fail closed when the token is missing or invalid.
The backend must still validate issuer, audience, tenant, expiry, scope, and subject type (see quickstart-backend-api.md); never authorize backend data from React state alone.
-->

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.

## What You Will Build

- 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

## Before You Start

<!-- agent:action Configure the SigID application
Have the user (or workspace admin) create a public browser application in Dashboard: choose the tenant, open Applications, click Create Application, then set the Allowed Callback URL, Allowed Logout URL, Allowed Web Origin, Allowed Origins (CORS), public PKCE client type, and scopes from this table.
If another admin owns the workspace, collect the issuer URL, client ID, callback URL, allowed origin, scopes, API audience, and tenant ID from their handoff before continuing.
-->

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](../business/workspace-admin-quickstart.md#5-hand-off-to-developers).

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

## Install

<!-- agent:action Install the SDK packages
Run: npm install @sigid/client @sigid/react
Run it at the React app root, using the project's existing package manager if it is not npm.
Verify both packages appear in package.json dependencies before continuing.
-->

```bash
npm install @sigid/client @sigid/react
```

## Configure The Client

<!-- agent:action Configure the client
Create src/sigid.ts exporting a sigid client built with createSigIdClient from @sigid/client: baseURL from VITE_SIGID_ISSUER_URL, clientId from VITE_SIGID_CLIENT_ID, redirectUri window.location.origin + "/auth/callback", scopes from VITE_SIGID_SCOPES split on whitespace.
Create .env.local with VITE_SIGID_ISSUER_URL, VITE_SIGID_CLIENT_ID, and VITE_SIGID_SCOPES.
If the project does not use Vite, swap the import.meta.env reads for the project's env mechanism.
-->

Create `src/sigid.ts`:

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

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

Create `.env.local`:

```bash
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

<!-- agent:action Mount the provider
Wrap the application root in SigIdProvider from @sigid/react, passing the sigid client exported by src/sigid.ts.
Place it above the router so every route, including the callback route, can use the SigID hooks.
If the app already has a root providers component, nest SigIdProvider inside it instead of replacing it.
-->

```tsx
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

<!-- agent:action Add sign-in and logout
On the home view, render SignInButton inside SignedOut and the user's email plus SignOutButton inside SignedIn, using useUser from @sigid/react.
Pass a returnTo path (the page's example uses "/account") so users land somewhere useful after login.
Verify before moving on: the signed-out state renders the button and clicking it redirects to the configured issuer.
-->

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

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

  return (
    <>
      <SignedOut>
        <SignInButton returnTo="/account">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

<!-- agent:action Add the callback route
Create a SigIdCallback component that calls handleCallback() from useSigId() in a useEffect and shows progress or a readable error message.
Register it in the app's router at exactly /auth/callback, matching the Allowed Callback URL configured in Dashboard.
A state error here usually means login was restarted in a different tab; retry login from the app.
-->

Render this component at `/auth/callback`:

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

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

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

  return <p>{message}</p>;
}
```

## Call Your Backend

<!-- agent:action Call the backend with auth
Use fetchWithAuth from useSigId() for backend requests so the bearer token is attached automatically; do not hand-build Authorization headers or read tokens into app state.
Handle non-ok responses explicitly so missing or invalid tokens surface as errors.
The backend must still validate issuer, audience, tenant, expiry, scope, and subject type; wire that with quickstart-backend-api.md.
-->

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

```tsx
import { useSigId } from "@sigid/react";

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

  async function loadProjects() {
    const response = await fetchWithAuth("/api/projects");
    if (!response.ok) throw new Error(`API failed: ${response.status}`);
    console.log(await response.json());
  }

  return <button onClick={() => void loadProjects()}>Load projects</button>;
}
```

Your backend must still validate issuer, audience, tenant, expiry, scope, and
subject type. Continue with [Backend API Quickstart](quickstart-backend-api.md).

## Verify

<!-- agent:action Verify the integration
Run the app and confirm: hosted login starts, the callback route completes without a state error, signed-in UI shows a user or session identifier without displaying tokens, logout returns to the signed-out UI, and 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 considering this done.
-->

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.
