HelloJohn / docs
Sdks

React SDK

Add authentication to any React application with @hellojohn/react — hooks, components, and context provider.

React SDK

@hellojohn/react provides hooks and components for adding authentication to React applications. It works with React 18+, Vite, Create React App, and any React-based framework.

Installation

npm install @hellojohn/react
# or
yarn add @hellojohn/react
# or
pnpm add @hellojohn/react

Setup

Wrap your app with HelloJohnProvider:

// main.tsx (or index.tsx)
import { HelloJohnProvider } from '@hellojohn/react';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <HelloJohnProvider publishableKey="pk_live_...">
    <App />
  </HelloJohnProvider>
);

Get your publishable key from the HelloJohn dashboard under Settings → API Keys.

Authentication hooks

useSignIn

import { useSignIn } from '@hellojohn/react';

function SignInForm() {
  const { signIn, isLoading, error } = useSignIn();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await signIn({ email, password });

    if (result.status === 'mfa_required') {
      // Navigate to MFA challenge page
    } else if (result.status === 'complete') {
      // Navigate to dashboard
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={e => setEmail(e.target.value)} type="email" />
      <input value={password} onChange={e => setPassword(e.target.value)} type="password" />
      {error && <p>{error.message}</p>}
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Signing in...' : 'Sign In'}
      </button>
    </form>
  );
}

useSignUp

import { useSignUp } from '@hellojohn/react';

function SignUpForm() {
  const { signUp, isLoading, error } = useSignUp();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await signUp({
      email: 'alice@example.com',
      password: 'strongpassword',
      displayName: 'Alice',
    });
    // User is signed in and session is set automatically
  };
}

useSignOut

import { useSignOut } from '@hellojohn/react';

function NavBar() {
  const { signOut } = useSignOut();

  return <button onClick={() => signOut()}>Sign Out</button>;
}

useUser

Access the current user from anywhere in your component tree:

import { useUser } from '@hellojohn/react';

function Profile() {
  const { user, isLoaded, isSignedIn } = useUser();

  if (!isLoaded) return <Spinner />;
  if (!isSignedIn) return <Redirect to="/sign-in" />;

  return (
    <div>
      <h1>Hello, {user.displayName}</h1>
      <p>{user.email}</p>
      <p>Role: {user.role}</p>
      <p>Tenant: {user.tenantId}</p>
    </div>
  );
}

useSession

Access the current session and tokens:

import { useSession } from '@hellojohn/react';

function ApiExample() {
  const { session, getToken } = useSession();

  const fetchData = async () => {
    const token = await getToken();  // Returns fresh access token (auto-refreshed)
    const res = await fetch('/api/data', {
      headers: { Authorization: `Bearer ${token}` },
    });
    return res.json();
  };
}
import { useMagicLink } from '@hellojohn/react';

function MagicLinkForm() {
  const { sendMagicLink, isLoading, isSent } = useMagicLink();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await sendMagicLink({ email: 'alice@example.com' });
  };

  if (isSent) return <p>Check your email!</p>;

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" />
      <button disabled={isLoading}>Send magic link</button>
    </form>
  );
}

useMFA

import { useMFA } from '@hellojohn/react';

function MFASetup() {
  const { enrollTOTP, verifyTOTP, unenroll, enrolledMethods } = useMFA();

  const startEnrollment = async () => {
    const { qrCodeUrl, secret } = await enrollTOTP();
    // Show QR code to user
    setQrCode(qrCodeUrl);
  };

  const completeEnrollment = async (code: string) => {
    await verifyTOTP({ code, isEnrollment: true });
    // MFA enrolled!
  };
}

Pre-built components

For faster integration, use the pre-built UI components:

import {
  SignIn,
  SignUp,
  UserButton,
  UserProfile,
} from '@hellojohn/react';

// Drop-in sign-in form
<SignIn afterSignInUrl="/dashboard" />

// Drop-in sign-up form
<SignUp afterSignUpUrl="/onboarding" />

// Avatar button with dropdown (sign out, manage account)
<UserButton afterSignOutUrl="/" />

// Full profile management page
<UserProfile />

Route protection

Higher-order component

import { withAuth } from '@hellojohn/react';

const DashboardPage = withAuth(function Dashboard() {
  return <div>Protected content</div>;
}, {
  redirectTo: '/sign-in',  // where to redirect if not signed in
});

Hook-based guard

import { useAuth } from '@hellojohn/react';
import { Navigate } from 'react-router-dom';

function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { isSignedIn, isLoaded } = useAuth();

  if (!isLoaded) return <Spinner />;
  if (!isSignedIn) return <Navigate to="/sign-in" />;

  return <>{children}</>;
}

HelloJohnProvider options

<HelloJohnProvider
  publishableKey="pk_live_..."
  // Optional: override API URL (for self-hosted)
  apiUrl="https://auth.yourdomain.com"
  // Optional: custom tenant ID
  tenantId="tnt_01H..."
  // Optional: token storage
  tokenStorage="cookie"     // "cookie" (default) | "localStorage" | "memory"
  // Optional: after sign-out URL
  afterSignOutUrl="/"
>
  <App />
</HelloJohnProvider>

TypeScript

The SDK is fully typed. Key types:

import type {
  User,
  Session,
  SignInResult,
  SignUpResult,
  AuthError,
  MFAMethod,
} from '@hellojohn/react';

React Native

For React Native, use @hellojohn/react-native instead — it handles native token storage and deep linking for magic links.

On this page