Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.hyperauth.dev/llms.txt

Use this file to discover all available pages before exploring further.

In this tutorial you’ll build a minimal React application that authenticates a user with a passkey, reads their decentralized identity, and prints it to the console. By the end you’ll have a running HyperAuth integration and a clear mental model of the three pieces that make it work: the provider, the client, and the useHyperAuth hook.

What you’ll build

A single-page React app that:
  1. Wraps the app in HyperAuthProvider so every component can reach the client.
  2. Reads the client status with useHyperAuth.
  3. Creates a passkey and generates an identity with client.generate().
  4. Logs the resulting DID and Ethereum address to the browser console.

Prerequisites

  • Node 18+ and a package manager (npm, pnpm, or bun).
  • A React project bootstrapped with Vite.
  • A browser that supports WebAuthn (Chrome, Firefox, or Safari).
  • The WASM artifacts available at /enclave.wasm and /vault.wasm in your app’s public/ directory.

Steps

1

Install the packages

Open a terminal at your project root and run:
npm install @hyperauth/sdk @hyperauth/react
@hyperauth/sdk is the core client. @hyperauth/react provides the React bindings.
2

Set up the WASM artifacts

HyperAuthProvider spawns two browser workers when it initialises: a Dedicated Worker that loads enclave.wasm (the stateless signer) and a SharedWorker that loads vault.wasm (the stateful vault). Both binaries must be reachable by your app.For local development, copy the binaries into your app’s public/ directory:
bun run compile:wasm
cp core/enclave/dist/enclave.wasm apps/my-app/public/
cp core/vault/dist/vault.wasm apps/my-app/public/
For production, set VITE_WASM_BASE_URL to point at the R2 CDN and pass the URLs through to the provider:
const wasmBase = import.meta.env.VITE_WASM_BASE_URL;
const clientConfig = wasmBase
  ? {
      wasmUrl: `${wasmBase}/enclave.wasm`,
      vaultWasmUrl: `${wasmBase}/vault.wasm`,
    }
  : undefined;
If VITE_WASM_BASE_URL is unset, the SDK defaults to relative paths — the local-dev path served from public/.
3

Wrap your app in HyperAuthProvider

Open your root file — typically src/main.tsx — and wrap your <App /> in HyperAuthProvider:
src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { HyperAuthProvider } from '@hyperauth/react';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <HyperAuthProvider>
      <App />
    </HyperAuthProvider>
  </React.StrictMode>,
);
HyperAuthProvider accepts an optional config prop. Omitting it uses the defaults, which is correct for local development when the WASM binaries are in public/. The provider spawns both workers in the background; your app continues rendering immediately.
4

Read the client with useHyperAuth

Create src/App.tsx with the following content:
src/App.tsx
import { useHyperAuth } from '@hyperauth/react';

export default function App() {
  const { client, status } = useHyperAuth();

  if (status === 'initializing') {
    return <p>Loading HyperAuth...</p>;
  }

  if (status === 'error') {
    return <p>HyperAuth failed to initialise. Check the console.</p>;
  }

  return (
    <div>
      <p>HyperAuth is ready.</p>
      <CreatePasskeyButton client={client!} />
    </div>
  );
}
useHyperAuth returns three values you’ll use constantly:
ValueTypeMeaning
clientHyperAuthClient | nullThe initialised client, or null while loading
status'initializing' | 'ready' | 'error'Current provider state
isReadybooleantrue when status === 'ready' and client !== null
5

Create a passkey and generate an identity

Add the CreatePasskeyButton component to the same file, below App:
src/App.tsx
import { useState } from 'react';
import { createPasskey } from '@hyperauth/sdk';
import type { HyperAuthClient } from '@hyperauth/sdk';

function CreatePasskeyButton({ client }: { client: HyperAuthClient }) {
  const [busy, setBusy] = useState(false);

  async function handleCreate() {
    setBusy(true);
    try {
      const { credential } = await createPasskey('demo-user');

      const result = await client.generate(credential, {
        identifier: 'demo-user',
      });

      console.log('DID:', result.did);
      console.log('Ethereum address:', result.address);
      console.log('Full result:', result);
    } finally {
      setBusy(false);
    }
  }

  return (
    <button onClick={handleCreate} disabled={busy}>
      {busy ? 'Creating...' : 'Create passkey'}
    </button>
  );
}
client.generate() returns a GenerateResult:
interface GenerateResult {
  success: boolean;
  did: string;          // e.g. "did:key:z6Mk..."
  address: string;      // Ethereum address derived from the public key
  shares: EncryptedShares & { enclave_id: string };
  parsed_credential: Record<string, unknown>;
}
Keep result.shares — it is the encrypted key material you’ll pass to client.sign() in later operations.
6

Run the app and see your identity

Start the dev server:
npm run dev
Open http://localhost:5173, click Create passkey, and follow the browser’s WebAuthn prompt. You’ll see two lines appear in the browser console:
DID: did:key:z6Mk...
Ethereum address: 0x...
No server round-trip is needed to generate the identity. The DID and address are derived entirely inside the signer Dedicated Worker running in your browser.

What you have built

You now have a working HyperAuth integration with passkey authentication. HyperAuthProvider manages both worker lifecycles: a Dedicated Worker per tab for stateless signing (enclave.wasm) and a SharedWorker per origin for stateful vault storage (vault.wasm). useHyperAuth gives any component access to the client, and client.generate() ties a passkey credential to a self-sovereign identity. The shares value returned by generate() is the key you’ll carry into signing and registration. The next tutorial, Build a Registration Flow, shows how to use it to publish your identity on-chain.