SDK QuickStart
Add Sign in with Humanity to your React app in under 10 minutes — same OAuth pattern as Google or GitHub, with biometric human verification built in.
Last updated
bun create vite hello-humanity-react-sdk-app --template react-tscd hello-humanity-react-sdk-appbun add @humanity-org/react-sdk react-router-dom
bun add -d vite-plugin-node-polyfills @types/nodeimport { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
export default defineConfig({
plugins: [
react(),
nodePolyfills({
include: ['crypto', 'buffer', 'stream', 'util'],
globals: { Buffer: true, process: true },
}),
],
server: { port: 3000 },
});VITE_HUMANITY_CLIENT_ID=app_your_client_id_here
VITE_HUMANITY_ENVIRONMENT=sandbox
VITE_REDIRECT_URI=http://localhost:3000/callbackimport React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { HumanityProvider } from '@humanity-org/react-sdk';
import '@humanity-org/react-sdk/styles.css';
import './index.css';
import App from './App';
const clientId = import.meta.env.VITE_HUMANITY_CLIENT_ID;
const environment = import.meta.env.VITE_HUMANITY_ENVIRONMENT || 'sandbox';
const redirectUri = import.meta.env.VITE_REDIRECT_URI || window.location.origin + '/callback';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<BrowserRouter>
<HumanityProvider
clientId={clientId}
redirectUri={redirectUri}
environment={environment as 'production' | 'sandbox'}
storage="sessionStorage"
onError={(error) => console.error('[Humanity]', error)}
>
<App />
</HumanityProvider>
</BrowserRouter>
</React.StrictMode>,
);import { Routes, Route, useNavigate } from 'react-router-dom';
import {
HumanityConnect,
useHumanity,
useVerification,
clearVerificationCache,
} from '@humanity-org/react-sdk';
import { useEffect } from 'react';
function IsHumanCheck() {
const { verify, result, isLoading } = useVerification();
useEffect(() => {
verify('is_human');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const isHuman = result?.value === true;
return (
<div className="status-card">
{isLoading && <p className="status-checking">Checking biometric status...</p>}
{!isLoading && isHuman && <p className="status-verified">✓ Biometrically verified</p>}
{!isLoading && !isHuman && <p className="status-unverified">✗ Not biometrically verified</p>}
{!isLoading && (
<button
className="btn-secondary"
onClick={() => { clearVerificationCache(); verify('is_human'); }}
>
Re-check
</button>
)}
</div>
);
}
function Home() {
const { isAuthenticated, isLoading, logout } = useHumanity();
if (isLoading) return <div className="screen"><p>Loading...</p></div>;
if (!isAuthenticated) {
return (
<div className="screen">
<h1>Humanity react-sdk-quickstart</h1>
<p>Connect your Humanity account to check your biometric verification status.</p>
<HumanityConnect
mode="redirect"
scopes={['openid', 'identity:read']}
onError={(err) => console.error(err)}
/>
</div>
);
}
return (
<div className="screen">
<h1>Humanity react-sdk-quickstart</h1>
<IsHumanCheck />
<button className="btn-ghost" onClick={logout}>Sign out</button>
</div>
);
}
function Callback() {
const { isAuthenticated, isLoading } = useHumanity();
const navigate = useNavigate();
useEffect(() => {
if (!isLoading && isAuthenticated) navigate('/', { replace: true });
}, [isAuthenticated, isLoading, navigate]);
return <div className="screen"><p>Completing sign-in...</p></div>;
}
export default function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/callback" element={<Callback />} />
</Routes>
);
}*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0a0a0a;
color: #ededed;
-webkit-font-smoothing: antialiased;
}
.screen {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1.5rem;
padding: 2rem;
text-align: center;
}
.screen h1 {
font-size: 1.75rem;
font-weight: 600;
}
.screen p {
color: #888;
font-size: 0.95rem;
}
.status-card {
border: 1px solid #2a2a2a;
border-radius: 12px;
padding: 2rem;
width: min(100%, 360px);
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
background: #141414;
}
.status-checking {
color: #666;
}
p.status-verified {
font-weight: 600;
font-size: 1.1rem;
color: #4ade80;
}
p.status-unverified {
font-weight: 600;
font-size: 1.1rem;
color: #f87171;
}
.btn-secondary {
padding: 0.5rem 1.25rem;
border: 1px solid #333;
border-radius: 8px;
background: #1f1f1f;
color: #ededed;
cursor: pointer;
font-size: 0.875rem;
}
.btn-ghost {
padding: 0.5rem 1.25rem;
border: 1px solid #2a2a2a;
border-radius: 8px;
background: none;
cursor: pointer;
font-size: 0.875rem;
color: #666;
}
.btn-secondary:hover { background: #2a2a2a; }
.btn-ghost:hover { background: #141414; }bun dev