React Integration Guide
Complete guide to integrating HelioRim passkey authentication in React applications.
Installation
npm install @heliorim/sdk-react
# or
yarn add @heliorim/sdk-react
# or
pnpm add @heliorim/sdk-reactSetup
1. Wrap Your App with Provider
// App.tsx
import { HelioRimProvider } from '@heliorim/sdk-react';
function App() {
const config = {
tenantId: process.env.REACT_APP_HELIORIM_TENANT_ID,
apiUrl: 'https://auth.api.heliorim.com',
appId: 'your-app-id'
};
return (
<HelioRimProvider config={config}>
<YourAppContent />
</HelioRimProvider>
);
}
export default App;2. Use Authentication Hooks
// components/LoginButton.tsx
import { useAuth, usePasskey } from '@heliorim/sdk-react';
function LoginButton() {
const { isAuthenticated, user, logout } = useAuth();
const { authenticate, isAuthenticating } = usePasskey();
if (isAuthenticated) {
return (
<div>
<span>Welcome, {user?.email}</span>
<button onClick={logout}>Sign Out</button>
</div>
);
}
return (
<button
onClick={() => authenticate()}
disabled={isAuthenticating}
>
{isAuthenticating ? 'Authenticating...' : 'Sign In with Passkey'}
</button>
);
}Available Hooks
useAuth
Authentication state and session management
usePasskey
Passkey registration and authentication
useRecovery
Account recovery operations
useTrust
Device trust and risk assessment
useLargeBlob
Store data in passkey credentials
usePRF
Pseudo-random function for encryption
useJWT
JWT token operations
useAdmin
Admin user management
useAudit
Audit logging and compliance
useCompliance
GDPR/CCPA compliance operations
Complete Example: Authentication Flow
// pages/AuthPage.tsx
import React, { useState } from 'react';
import {
useAuth,
usePasskey,
useRecovery,
useHelioRim
} from '@heliorim/sdk-react';
function AuthPage() {
const { isAuthenticated, user, logout } = useAuth();
const { register, authenticate, credentials, deleteCredential } = usePasskey();
const { generateBackupCodes } = useRecovery();
const { isSupported, capabilities } = useHelioRim();
const [email, setEmail] = useState('');
const [displayName, setDisplayName] = useState('');
const [mode, setMode] = useState<'login' | 'register'>('login');
if (!isSupported) {
return (
<div className="alert alert-warning">
Your browser doesn't support passkeys.
Please use a modern browser.
</div>
);
}
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
try {
// Register new passkey
const credential = await register({
email: email,
displayName: displayName,
userVerification: 'required'
});
// Generate backup codes
const codes = await generateBackupCodes();
console.log('Save these backup codes:', codes);
alert('Registration successful!');
} catch (error) {
console.error('Registration failed:', error);
}
};
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
try {
await authenticate({
email: email ?? undefined,
userVerification: 'required'
});
alert('Login successful!');
} catch (error) {
console.error('Login failed:', error);
}
};
if (isAuthenticated) {
return (
<div className="dashboard">
<h2>Welcome, {user?.email}</h2>
<div className="credentials-section">
<h3>Your Passkeys</h3>
{credentials.map(cred => (
<div key={cred.id} className="credential-item">
<span>{cred.name}</span>
<span>{new Date(cred.createdAt).toLocaleDateString()}</span>
<button onClick={() => deleteCredential(cred.id)}>
Remove
</button>
</div>
))}
</div>
<button onClick={logout} className="btn-logout">
Sign Out
</button>
</div>
);
}
return (
<div className="auth-container">
<div className="auth-tabs">
<button
className={mode === 'login' ? 'active' : ''}
onClick={() => setMode('login')}
>
Sign In
</button>
<button
className={mode === 'register' ? 'active' : ''}
onClick={() => setMode('register')}
>
Register
</button>
</div>
{mode === 'login' ? (
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email (optional for returning users)"
/>
<button type="submit">Sign In with Passkey</button>
</form>
) : (
<form onSubmit={handleRegister}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="Display Name"
required
/>
<button type="submit">Create Account with Passkey</button>
</form>
)}
</div>
);
}
export default AuthPage;TypeScript Support
// types/auth.ts
import type {
UseAuthReturn,
UsePasskeyReturn,
Session,
Credential,
WebAuthnCapabilities
} from '@heliorim/sdk-react';
// All types are fully typed and exported
interface AuthContextValue {
auth: UseAuthReturn;
passkey: UsePasskeyReturn;
session: Session | null;
}
// Custom hook with types
function useAuthContext(): AuthContextValue {
const auth = useAuth();
const passkey = usePasskey();
return {
auth,
passkey,
session: auth.session
};
}Protected Routes
// components/ProtectedRoute.tsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '@heliorim/sdk-react';
interface ProtectedRouteProps {
children: React.ReactNode;
redirectTo?: string;
}
export function ProtectedRoute({
children,
redirectTo = '/login'
}: ProtectedRouteProps) {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return <Navigate to={redirectTo} replace />;
}
return <>{children}</>;
}
// Usage in App.tsx
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>Error Handling
import { useAuth, usePasskey } from '@heliorim/sdk-react';
function ErrorBoundaryAuth() {
const { error: authError, clearError: clearAuthError } = useAuth();
const { error: passkeyError, clearError: clearPasskeyError } = usePasskey();
// Global error display
if (authError ?? passkeyError) {
return (
<div className="error-banner">
<p>{authError?.message ?? passkeyError?.message}</p>
<button onClick={() => {
clearAuthError();
clearPasskeyError();
}}>
Dismiss
</button>
</div>
);
}
return null;
}
// Specific error handling
async function handleAuthentication() {
try {
await authenticate();
} catch (error) {
if (error.name === 'NotAllowedError') {
// User cancelled
showToast('Authentication cancelled');
} else if (error.name === 'InvalidStateError') {
// Credential already exists
showToast('This device is already registered');
} else {
// Generic error
showToast('Authentication failed. Please try again.');
}
}
}Best Practices
✓ Recommended
- Always wrap your app with
HelioRimProvider - Check
isSupportedbefore showing passkey UI - Handle loading states with
isLoadingflags - Provide clear error messages to users
- Use TypeScript for better type safety
- Implement proper error boundaries
✗ Avoid
- Don't call hooks outside React components
- Don't store sensitive data in localStorage
- Don't skip user verification for sensitive ops
- Don't assume passkeys are available