React SDK

The React SDK (@reauth-dev/sdk/react) provides hooks and components for React and Next.js apps.

Installation

npm install @reauth-dev/sdk

React is a peer dependency (>= 18.0.0).

Exports

import {
  AuthProvider,
  useAuthContext,
  useAuth,
  useHeadlessAuth,
  ProtectedRoute,
} from '@reauth-dev/sdk/react';

AuthProvider + useAuthContext

The recommended pattern for Next.js App Router apps. Wrap your app in AuthProvider and access auth state anywhere with useAuthContext.

// app/providers.tsx
'use client';
import { AuthProvider } from '@reauth-dev/sdk/react';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <AuthProvider config={{ domain: 'yourdomain.com' }}>
      {children}
    </AuthProvider>
  );
}
// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
// Any component inside AuthProvider
'use client';
import { useAuthContext } from '@reauth-dev/sdk/react';

export function UserMenu() {
  const { user, logout } = useAuthContext();

  return (
    <div>
      {user?.email}
      <button onClick={logout}>Sign out</button>
    </div>
  );
}

AuthProvider Props

type AuthProviderProps = {
  config: {
    domain: string;         // Your verified domain
    refreshInterval?: number; // Session check interval in ms (default: 300000 / 5 min)
    timeout?: number;        // Request timeout in ms (default: 10000)
  };
  children: ReactNode;
};

useAuthContext Return Value

{
  user: User | null;           // Authenticated user, or null
  loading: boolean;            // True during initial session check
  error: string | null;        // Error message if auth check failed
  isOnWaitlist: boolean;       // True if user is on waitlist
  waitlistPosition: number | null;
  login: () => void;           // Redirect to hosted login page
  logout: () => Promise<void>; // Clear session and reset state
  refetch: () => Promise<void>; // Manually re-check session
}

The User type:

type User = {
  id: string;
  email: string;
  roles: string[];
  activeOrgId: string;
  orgRole: string;  // "owner" | "member"
};

useAuth (Standalone Hook)

If you don't want to use context, useAuth provides the same functionality as a standalone hook:

'use client';
import { useAuth } from '@reauth-dev/sdk/react';

export default function Page() {
  const { user, loading, login, logout } = useAuth({
    domain: 'yourdomain.com',
    refreshInterval: 60000, // Check every minute
  });

  if (loading) return <div>Loading...</div>;
  if (!user) return <button onClick={login}>Sign in</button>;

  return (
    <div>
      Welcome {user.email}
      <button onClick={logout}>Sign out</button>
    </div>
  );
}

Returns the same shape as useAuthContext.

ProtectedRoute

Wraps content that requires authentication. Automatically redirects unauthenticated users to the login page.

import { ProtectedRoute } from '@reauth-dev/sdk/react';

// Basic usage — redirects to hosted login if not authenticated
<ProtectedRoute>
  <Dashboard />
</ProtectedRoute>

// With loading fallback
<ProtectedRoute fallback={<LoadingSpinner />}>
  <Dashboard />
</ProtectedRoute>

// With custom handlers
<ProtectedRoute
  onUnauthenticated={() => router.push('/login')}
  onWaitlist={() => router.push('/waitlist')}
>
  <Dashboard />
</ProtectedRoute>

Props

type ProtectedRouteProps = {
  children: ReactNode;
  fallback?: ReactNode;              // Shown while loading (default: null)
  onUnauthenticated?: () => void;    // Custom redirect (default: calls login())
  onWaitlist?: () => void;           // Custom waitlist handler
};

useHeadlessAuth

For building custom login UIs without the hosted login page. Requires headless mode to be enabled in the dashboard.

'use client';
import { useHeadlessAuth } from '@reauth-dev/sdk/react';

export default function LoginPage() {
  const {
    requestMagicLink,
    verifyMagicLink,
    startGoogleOAuth,
    startTwitterOAuth,
    loading,
    error,
    step,
    config,
    getConfig,
    reset,
  } = useHeadlessAuth({
    domain: 'yourdomain.com',
    callbackUrl: 'https://yourdomain.com/auth/verify',
  });

  if (step === 'magic_link_sent') {
    return <p>Check your email for a magic link!</p>;
  }

  if (step === 'completed') {
    return <p>Authenticated! Redirecting...</p>;
  }

  return (
    <form onSubmit={async (e) => {
      e.preventDefault();
      const email = new FormData(e.currentTarget).get('email') as string;
      await requestMagicLink(email);
    }}>
      <input name="email" type="email" placeholder="Enter your email" />
      <button type="submit" disabled={loading}>Send Magic Link</button>
      <button type="button" onClick={startGoogleOAuth} disabled={loading}>
        Sign in with Google
      </button>
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </form>
  );
}

Verification Page

// app/auth/verify/page.tsx
'use client';
import { useEffect } from 'react';
import { useHeadlessAuth } from '@reauth-dev/sdk/react';

export default function VerifyPage() {
  const { verifyMagicLink, step, error } = useHeadlessAuth({
    domain: 'yourdomain.com',
  });

  useEffect(() => {
    const token = new URLSearchParams(window.location.search).get('token');
    if (token) {
      verifyMagicLink(token).then((result) => {
        if (result.success) {
          window.location.href = result.redirectUrl || '/dashboard';
        }
      });
    }
  }, []);

  if (error) return <p>Verification failed: {error}</p>;
  return <p>Verifying...</p>;
}

Options

type UseHeadlessAuthOptions = {
  domain: string;
  callbackUrl?: string; // URL for magic link emails to point to
  timeout?: number;     // Request timeout in ms
};

Return Value

{
  loading: boolean;
  error: string | null;
  step: 'idle' | 'magic_link_sent' | 'google_started' | 'twitter_started' | 'completed';
  config: DomainConfig | null;
  verifyResult: MagicLinkVerifyResult | null;
  getConfig: () => Promise<DomainConfig>;
  requestMagicLink: (email: string) => Promise<void>;
  verifyMagicLink: (token: string) => Promise<MagicLinkVerifyResult>;
  startGoogleOAuth: () => Promise<GoogleOAuthStartResult>;
  startTwitterOAuth: () => Promise<TwitterOAuthStartResult>;
  reset: () => void;  // Reset to idle state
}

Token Management for API Calls

To call your own protected API endpoints, use getToken() from the browser client:

'use client';
import { createReauthClient } from '@reauth-dev/sdk';
import { useAuthContext } from '@reauth-dev/sdk/react';

const reauth = createReauthClient({ domain: 'yourdomain.com' });

export function useFetchWithAuth() {
  const { user } = useAuthContext();

  return async (url: string, options?: RequestInit) => {
    const tokenResponse = await reauth.getToken();
    if (!tokenResponse) throw new Error('Not authenticated');

    return fetch(url, {
      ...options,
      headers: {
        ...options?.headers,
        Authorization: `Bearer ${tokenResponse.accessToken}`,
      },
    });
  };
}

Alternatively, if your API and auth share the same parent domain, session cookies are sent automatically with credentials: 'include'.