npm package: @passkeyme/react-auth
Description: React integration for Passkeyme Authentication SDK - Simple single dependency installation
Author: Passkeyme
Latest version: 2.3.3

# @passkeyme/react-auth

React integration for Passkeyme Authentication SDK. Build secure authentication into your React apps with hooks and components.

## 🚀 Features

- **🪝 React Hooks**: `usePasskeyme()`, `useAuth()`, `useAuthState()`
- **🧩 Pre-built Components**: Login buttons, user profiles, protected routes
- **🔄 Automatic State Management**: Context-based state with automatic updates
- **🎨 Customizable UI**: Styled components with full customization options
- **🛡️ Type Safety**: Full TypeScript support
- **⚡ Zero Config**: Works out of the box with sensible defaults

## 📦 Installation

```bash
npm install @passkeyme/auth @passkeyme/react-auth
```

**Peer Dependencies:**

- React >= 16.8.0
- React DOM >= 16.8.0

## 🔧 Quick Start

### 1. Setup Provider

Wrap your app with the `PasskeymeProvider`:

```tsx
import { PasskeymeProvider } from "@passkeyme/react-auth";

function App() {
  return (
    <PasskeymeProvider
      config={{
        appId: "your-passkeyme-app-id",
        redirectUri: "http://localhost/auth/callback",
        debug: process.env.NODE_ENV === "development", // Enable debug logging in development
      }}
    >
      <YourApp />
    </PasskeymeProvider>
  );
}
```

### 2. Use Authentication Hooks

```tsx
import { usePasskeyme, PasskeymeCallbackHandler } from "@passkeyme/react-auth";
import { BrowserRouter, Routes, Route } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* Built-in callback handler - handles all authentication flows */}
        <Route path="/auth/callback" element={<PasskeymeCallbackHandler />} />
        <Route path="/" element={<Dashboard />} />
      </Routes>
    </BrowserRouter>
  );
}

function Dashboard() {
  const { user, loginWithOAuth, logout, loading } = usePasskeyme();

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!user) {
    return (
      <button onClick={() => loginWithOAuth("google")}>
        Login with Google
      </button>
    );
  }

  return (
    <div>
      <h1>Welcome, {user.name}!</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}
```

### 3. Use Pre-built Components

```tsx
import {
  PasskeymeOAuthButton,
  PasskeymeButton,
  PasskeymeUserProfile,
  PasskeymeProtectedRoute,
} from "@passkeyme/react-auth";

function LoginPage() {
  return (
    <div>
      <PasskeymeOAuthButton provider="google" />
      <PasskeymeOAuthButton provider="github" />
      <PasskeymeButton />
    </div>
  );
}

function Dashboard() {
  return (
    <PasskeymeProtectedRoute fallback={<LoginPage />}>
      <div>
        <PasskeymeUserProfile showLogout />
        <h1>Protected Content</h1>
      </div>
    </PasskeymeProtectedRoute>
  );
}
```

## 📚 API Reference

### PasskeymeProvider

The main provider component that manages authentication state.

```tsx
<PasskeymeProvider
  config={{
    appId: "your-app-id",
    redirectUri: "http://localhost:3000/callback",
    debug: true, // Enable debug logging for development
  }}
  loadingComponent={<div>Loading...</div>}
  errorComponent={error => <div>Error: {error}</div>}
  onAuthChange={user => console.log("User changed:", user)}
  onError={error => console.error("Auth error:", error)}
>
  <App />
</PasskeymeProvider>
```

> **Note**: Set `debug: false` or omit the debug flag in production to prevent debug messages from appearing in the browser console.

### usePasskeyme Hook

Main hook providing full authentication functionality.

```tsx
const {
  user, // Current user or null
  loading, // Loading state
  error, // Error message or null
  isAuthenticated, // Boolean auth status

  // Login methods
  login, // Generic login with options
  loginWithOAuth, // OAuth login (redirects)
  loginWithPasskey, // Passkey authentication
  loginWithPassword, // Username/password login
  handleCallback, // Handle OAuth callback

  // Other methods
  logout, // Logout user
  getAccessToken, // Get current token
  refreshToken, // Refresh token
  auth, // Direct access to auth instance
} = usePasskeyme();
```

### useAuthState Hook

Get only the authentication state (no methods).

```tsx
const { user, loading, error, isAuthenticated } = useAuthState();
```

### useAuth Hook

Get only the authentication methods (no state).

```tsx
const {
  login,
  loginWithOAuth,
  loginWithPasskey,
  loginWithPassword,
  logout,
  getAccessToken,
  refreshToken,
} = useAuth();
```

## 🧩 Components

### PasskeymeCallbackHandler

**⭐ NEW**: Built-in component that automatically handles all authentication callbacks.

The `PasskeymeCallbackHandler` eliminates the need to write custom callback logic. Just add it to your routing:

```tsx
import { PasskeymeCallbackHandler } from '@passkeyme/react-auth';

// Basic usage - handles everything automatically
<Route path="/callback" element={<PasskeymeCallbackHandler />} />

// With custom redirects and components
<Route
  path="/callback"
  element={
    <PasskeymeCallbackHandler
      successRedirect="/dashboard"
      errorRedirect="/login"
      loadingComponent={CustomSpinner}
      errorComponent={CustomError}
    />
  }
/>
```

**Features:**

- ✅ Handles both OAuth (`?code=...`) and hosted auth (`?token=...`) flows
- ✅ Built-in loading and error states with customizable components
- ✅ Automatic token validation and user state management
- ✅ **NEW**: Automatic passkey registration prompting (enabled by default)
- ✅ Configurable success/error redirects
- ✅ Custom event callbacks for advanced use cases
- ✅ URL cleanup after processing

See [CALLBACK_HANDLER.md](./CALLBACK_HANDLER.md) for complete documentation.

### PasskeymeOAuthButton

OAuth login button with built-in styling.

```tsx
<PasskeymeOAuthButton
  provider="google" // 'google' | 'github' | 'facebook'
  variant="default" // 'default' | 'outlined' | 'text'
  size="medium" // 'small' | 'medium' | 'large'
  redirectUri="/custom/callback"
  disabled={false}
  loading={false}
  onClick={() => console.log("Clicked")}
  className="custom-class"
>
  Custom Button Text
</PasskeymeOAuthButton>
```

### PasskeymeButton

Passkey authentication button.

```tsx
<PasskeymeButton
  username="user@example.com" // Optional username hint
  variant="default"
  size="medium"
  onSuccess={user => console.log("Success:", user)}
  onError={error => console.log("Error:", error)}
>
  Sign in with Passkey
</PasskeymeButton>
```

### PasskeymeUserProfile

User profile display with avatar and logout.

```tsx
<PasskeymeUserProfile
  showAvatar={true}
  showName={true}
  showEmail={true}
  showLogout={true}
  avatarSize={40}
  logoutText="Sign Out"
  onLogout={() => console.log("Logged out")}
  className="profile-container"
/>
```

### PasskeymeProtectedRoute

Protect content for authenticated users.

```tsx
<PasskeymeProtectedRoute
  fallback={<LoginPage />}
  redirectTo="/login"
  requiredRoles={["admin", "user"]}
  hasAccess={user => user.emailVerified}
>
  <SecretContent />
</PasskeymeProtectedRoute>
```

### withAuth HOC

Higher-order component for protecting components.

```tsx
const ProtectedComponent = withAuth(MyComponent, {
  fallback: <div>Please login</div>,
  requiredRoles: ["admin"],
});
```

## 🚀 Programmatic Authentication

### triggerPasskeymeAuth

**⭐ NEW**: Enhanced programmatic authentication with mode support for hosted vs inline OAuth flows.

```tsx
// Simple hosted flow (default - redirects to hosted auth pages)
const { triggerPasskeymeAuth } = usePasskeyme();
triggerPasskeymeAuth();

// Custom success/error handling
triggerPasskeymeAuth({
  username: "user@example.com",
  onSuccess: (user, method) => console.log(`Authenticated via ${method}`, user),
  onError: error => console.error("Auth failed:", error),
});

// Inline mode - developer controls OAuth UI
triggerPasskeymeAuth({
  mode: "inline",
  onOAuthRequired: providers => {
    // Show your custom OAuth UI
    setShowOAuthModal(true);
    setAvailableProviders(providers);
  },
  onSuccess: (user, method) => {
    setShowOAuthModal(false);
    console.log(`Success via ${method}`, user);
  },
});

// Passkey-only (no fallback)
triggerPasskeymeAuth({
  forcePasskeyOnly: true,
  onError: error => alert("Passkey authentication failed"),
});
```

**Mode Options:**

- **`hosted`** (default): Try passkey → redirect to hosted auth pages
- **`inline`**: Try passkey → callback with OAuth providers for custom UI

This gives you maximum flexibility - use hosted mode for simple setup, or inline mode for full UI control.

### Complete Inline Mode Example

```tsx
function CustomAuthFlow() {
  const { triggerPasskeymeAuth } = usePasskeyme();
  const [showOAuth, setShowOAuth] = useState(false);
  const [providers, setProviders] = useState([]);

  const handleLogin = () => {
    triggerPasskeymeAuth({
      mode: "inline",
      onOAuthRequired: availableProviders => {
        setProviders(availableProviders);
        setShowOAuth(true);
      },
      onSuccess: (user, method) => {
        setShowOAuth(false);
        console.log(`Authenticated via ${method}`);
      },
    });
  };

  return (
    <div>
      <button onClick={handleLogin}>Login</button>

      {showOAuth && (
        <div className="oauth-modal">
          <h3>Choose OAuth Provider</h3>
          {providers.map(provider => (
            <PasskeymeOAuthButton key={provider} provider={provider}>
              Login with {provider}
            </PasskeymeOAuthButton>
          ))}
          <button onClick={() => setShowOAuth(false)}>Cancel</button>
        </div>
      )}
    </div>
  );
}
```

## 🎨 Customization

### Custom Styling

All components accept `className` and can be styled with CSS:

```css
.login-button {
  background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%);
  border-radius: 20px;
}

.user-profile {
  border: 2px solid #f0f0f0;
  border-radius: 8px;
  padding: 16px;
}
```

### Custom Components

Create your own components using the hooks:

```tsx
function CustomLoginForm() {
  const { loginWithPassword, loading, error } = usePasskeyme();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = async e => {
    e.preventDefault();
    try {
      await loginWithPassword(email, password);
    } catch (err) {
      // Error handled by hook
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={email}
        onChange={e => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={e => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="submit" disabled={loading}>
        {loading ? "Signing in..." : "Sign In"}
      </button>
      {error && <div>Error: {error}</div>}
    </form>
  );
}
```

## 🔗 Integration Examples

### Next.js App Router

```tsx
// app/layout.tsx
import { PasskeymeProvider } from "@passkeyme/react-auth";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <PasskeymeProvider
          config={{ appId: process.env.NEXT_PUBLIC_PASSKEYME_APP_ID }}
        >
          {children}
        </PasskeymeProvider>
      </body>
    </html>
  );
}

// app/dashboard/page.tsx
import { ProtectedRoute, UserProfile } from "@passkeyme/react-auth";

export default function Dashboard() {
  return (
    <ProtectedRoute>
      <div>
        <UserProfile />
        <h1>Dashboard</h1>
      </div>
    </ProtectedRoute>
  );
}
```

### React Router

```tsx
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
import { usePasskeyme } from "@passkeyme/react-auth";

function AppRouter() {
  const { isAuthenticated, loading } = usePasskeyme();

  if (loading) return <div>Loading...</div>;

  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        <Route
          path="/dashboard"
          element={isAuthenticated ? <Dashboard /> : <Navigate to="/login" />}
        />
      </Routes>
    </BrowserRouter>
  );
}
```

## 🔗 TypeScript Support

This package provides comprehensive TypeScript definitions for type-safe authentication.

### Type Imports

```typescript
import type {
  User,
  AuthConfig,
  AuthState,
  PasskeyCredential,
  OAuthProvider,
  UsePasskeymeReturn,
} from "@passkeyme/react-auth";

// User object structure
interface User {
  id: string;
  email: string;
  name?: string;
  avatar?: string;
  metadata?: Record<string, any>;
}

// Authentication configuration
interface AuthConfig {
  appId: string;
  baseUrl?: string;
  redirectUri: string;
  debug?: boolean;
}

// Hook return type
interface UsePasskeymeReturn {
  // State
  user: User | null;
  isAuthenticated: boolean;
  loading: boolean;
  error: Error | null;

  // Actions
  loginWithOAuth: (provider: OAuthProvider) => Promise<void>;
  loginWithPasskey: () => Promise<void>;
  loginWithCredentials: (email: string, password: string) => Promise<void>;
  register: (email: string, password: string, name?: string) => Promise<void>;
  logout: () => Promise<void>;

  // Utility
  refreshToken: () => Promise<void>;
  getAuthHeader: () => string | null;
}
```

### Type-Safe Configuration

```typescript
import { PasskeymeProvider } from "@passkeyme/react-auth";
import type { AuthConfig } from "@passkeyme/react-auth";

const authConfig: AuthConfig = {
  appId: process.env.REACT_APP_PASSKEYME_APP_ID!,
  redirectUri: `${window.location.origin}/auth/callback`,
  debug: process.env.NODE_ENV === "development",
};

function App() {
  return (
    <PasskeymeProvider config={authConfig}>
      <AppContent />
    </PasskeymeProvider>
  );
}
```

### Custom Hook with TypeScript

```typescript
import { usePasskeyme } from "@passkeyme/react-auth";
import type { User } from "@passkeyme/react-auth";

// Custom hook for user profile management
function useUserProfile() {
  const { user, updateProfile } = usePasskeyme();

  const updateUserProfile = async (updates: Partial<User>) => {
    if (!user) throw new Error("User not authenticated");

    return updateProfile({
      ...user,
      ...updates,
    });
  };

  return {
    user,
    updateUserProfile,
    hasProfile: Boolean(user?.name),
    isEmailVerified: user?.emailVerified ?? false,
  };
}
```

## 🔄 Integration Guides

### Next.js Integration

```typescript
// pages/_app.tsx
import { AppProps } from "next/app";
import { PasskeymeProvider } from "@passkeyme/react-auth";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <PasskeymeProvider
      config={{
        appId: process.env.NEXT_PUBLIC_PASSKEYME_APP_ID!,
        redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/auth/callback`,
      }}
    >
      <Component {...pageProps} />
    </PasskeymeProvider>
  );
}

// pages/auth/callback.tsx
import { PasskeymeCallbackHandler } from "@passkeyme/react-auth";

export default function AuthCallback() {
  return <PasskeymeCallbackHandler />;
}

// Custom hook for Next.js router integration
import { useRouter } from "next/router";
import { usePasskeyme } from "@passkeyme/react-auth";
import { useEffect } from "react";

export function useAuthRedirect() {
  const { isAuthenticated, loading } = usePasskeyme();
  const router = useRouter();

  useEffect(() => {
    if (!loading && !isAuthenticated) {
      router.push("/login");
    }
  }, [isAuthenticated, loading, router]);

  return { isAuthenticated, loading };
}
```

### Vite/React Integration

```typescript
// main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { PasskeymeProvider } from "@passkeyme/react-auth";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <PasskeymeProvider
      config={{
        appId: import.meta.env.VITE_PASSKEYME_APP_ID,
        redirectUri: `${window.location.origin}/auth/callback`,
        debug: import.meta.env.DEV,
      }}
    >
      <App />
    </PasskeymeProvider>
  </React.StrictMode>
);

// vite-env.d.ts - Environment variables
interface ImportMetaEnv {
  readonly VITE_PASSKEYME_APP_ID: string;
  // Add other env variables as needed
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}
```

### React Router Integration

```typescript
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { PasskeymeCallbackHandler, usePasskeyme } from "@passkeyme/react-auth";

// Protected Route Component
interface ProtectedRouteProps {
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

function ProtectedRoute({ children, fallback }: ProtectedRouteProps) {
  const { isAuthenticated, loading } = usePasskeyme();

  if (loading) return <div>Loading...</div>;

  if (!isAuthenticated) {
    return fallback ? <>{fallback}</> : <Navigate to="/login" replace />;
  }

  return <>{children}</>;
}

function AppRouter() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        <Route path="/auth/callback" element={<PasskeymeCallbackHandler />} />
        <Route
          path="/dashboard"
          element={
            <ProtectedRoute>
              <Dashboard />
            </ProtectedRoute>
          }
        />
        <Route path="/" element={<Navigate to="/dashboard" replace />} />
      </Routes>
    </BrowserRouter>
  );
}
```

### Error Handling Best Practices

```typescript
import { usePasskeyme } from "@passkeyme/react-auth";
import { useEffect, useState } from "react";

function LoginWithErrorHandling() {
  const { loginWithOAuth, error, loading } = usePasskeyme();
  const [loginError, setLoginError] = useState<string | null>(null);

  const handleLogin = async (provider: "google" | "github") => {
    try {
      setLoginError(null);
      await loginWithOAuth(provider);
    } catch (err) {
      setLoginError(err instanceof Error ? err.message : "Login failed");
    }
  };

  // Handle authentication errors
  useEffect(() => {
    if (error) {
      console.error("Authentication error:", error);
      setLoginError(error.message);
    }
  }, [error]);

  return (
    <div>
      {loginError && (
        <div className="error-banner" role="alert">
          {loginError}
          <button onClick={() => setLoginError(null)}>×</button>
        </div>
      )}

      <button onClick={() => handleLogin("google")} disabled={loading}>
        {loading ? "Signing in..." : "Sign in with Google"}
      </button>
    </div>
  );
}
```

## 🧪 Testing Integration

### Jest Testing Setup

```typescript
// test-utils.tsx
import React from "react";
import { render, RenderOptions } from "@testing-library/react";
import { PasskeymeProvider } from "@passkeyme/react-auth";
import type { AuthConfig } from "@passkeyme/react-auth";

const mockAuthConfig: AuthConfig = {
  appId: "test-app-id",
  redirectUri: "http://localhost:3000/callback",
  debug: false,
};

interface CustomRenderOptions extends Omit<RenderOptions, "wrapper"> {
  authConfig?: Partial<AuthConfig>;
}

export function renderWithAuth(
  ui: React.ReactElement,
  { authConfig = {}, ...options }: CustomRenderOptions = {}
) {
  const config = { ...mockAuthConfig, ...authConfig };

  function Wrapper({ children }: { children: React.ReactNode }) {
    return <PasskeymeProvider config={config}>{children}</PasskeymeProvider>;
  }

  return render(ui, { wrapper: Wrapper, ...options });
}

// Component test example
import { screen, fireEvent, waitFor } from "@testing-library/react";
import { usePasskeyme } from "@passkeyme/react-auth";

function TestComponent() {
  const { user, loginWithOAuth, loading } = usePasskeyme();

  return (
    <div>
      {user ? (
        <div data-testid="user-info">{user.email}</div>
      ) : (
        <button onClick={() => loginWithOAuth("google")}>
          {loading ? "Loading..." : "Login"}
        </button>
      )}
    </div>
  );
}

test("handles login flow", async () => {
  renderWithAuth(<TestComponent />);

  const loginButton = screen.getByText("Login");
  fireEvent.click(loginButton);

  expect(screen.getByText("Loading...")).toBeInTheDocument();

  await waitFor(() => {
    expect(screen.getByTestId("user-info")).toBeInTheDocument();
  });
});
```

## 🔗 Package Integration

### Using with @passkeyme/react-core

This package builds on top of `@passkeyme/react-core` for shared patterns:

```typescript
// Access core patterns directly
import {
  usePasskeymeContext,
  PasskeymeProvider as CoreProvider,
} from "@passkeyme/react-core";

// Or use the enhanced React-specific provider
import { PasskeymeProvider } from "@passkeyme/react-auth";

// Custom hook using core patterns
function useCustomAuth() {
  const { state, dispatch } = usePasskeymeContext();

  const customLogin = async () => {
    dispatch({ type: "SET_LOADING", payload: true });
    try {
      // Custom authentication logic
      const user = await customAuthFlow();
      dispatch({ type: "SET_USER", payload: user });
    } catch (error) {
      dispatch({ type: "SET_ERROR", payload: error });
    } finally {
      dispatch({ type: "SET_LOADING", payload: false });
    }
  };

  return { ...state, customLogin };
}
```

### Using with @passkeyme/auth Core

```typescript
import { PasskeymeAuth } from "@passkeyme/auth";
import { usePasskeyme } from "@passkeyme/react-auth";

// Access the core auth instance
function AdvancedAuthComponent() {
  const { auth } = usePasskeyme();

  const handleAdvancedFlow = async () => {
    // Direct access to core SDK methods
    const tokens = await auth.getTokens();
    const userInfo = await auth.getUserInfo();

    // Custom API calls with auth headers
    const response = await fetch("/api/protected", {
      headers: {
        Authorization: `Bearer ${tokens.accessToken}`,
      },
    });
  };

  return <button onClick={handleAdvancedFlow}>Advanced Flow</button>;
}
```

## 🆚 vs Other Solutions

| Feature               | **@passkeyme/react-auth** | **Firebase Auth** | **Auth0 React**    |
| --------------------- | ------------------------- | ----------------- | ------------------ |
| **Setup Lines**       | ✅ 5 lines                | ❌ 20+ lines      | ❌ 15+ lines       |
| **Built-in Passkeys** | ✅ Native support         | ❌ Manual setup   | ❌ Manual setup    |
| **Type Safety**       | ✅ Full TypeScript        | ⚠️ Partial        | ✅ Full TypeScript |
| **Bundle Size**       | ✅ Small                  | ❌ Large          | ❌ Large           |
| **Self-Hosting**      | ✅ Yes                    | ❌ No             | ❌ No              |
| **Vendor Lock-in**    | ✅ None                   | ❌ High           | ❌ High            |

## 📄 License

MIT - see LICENSE file for details.

## 🤝 Support

- **Documentation**: https://passkeyme.com/docs/react
- **Issues**: https://github.com/passkeyme/passkeyme/issues
- **Discord**: https://discord.gg/passkeyme