@passkeyme/react-auth
v2.3.3
Published
React integration for Passkeyme Authentication SDK - Simple single dependency installation
Maintainers
Readme
@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
npm install @passkeyme/auth @passkeyme/react-authPeer Dependencies:
- React >= 16.8.0
- React DOM >= 16.8.0
🔧 Quick Start
1. Setup Provider
Wrap your app with the PasskeymeProvider:
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
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
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.
<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: falseor omit the debug flag in production to prevent debug messages from appearing in the browser console.
usePasskeyme Hook
Main hook providing full authentication functionality.
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).
const { user, loading, error, isAuthenticated } = useAuthState();useAuth Hook
Get only the authentication methods (no state).
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:
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 for complete documentation.
PasskeymeOAuthButton
OAuth login button with built-in styling.
<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.
<PasskeymeButton
username="[email protected]" // 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.
<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.
<PasskeymeProtectedRoute
fallback={<LoginPage />}
redirectTo="/login"
requiredRoles={["admin", "user"]}
hasAccess={user => user.emailVerified}
>
<SecretContent />
</PasskeymeProtectedRoute>withAuth HOC
Higher-order component for protecting components.
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.
// Simple hosted flow (default - redirects to hosted auth pages)
const { triggerPasskeymeAuth } = usePasskeyme();
triggerPasskeymeAuth();
// Custom success/error handling
triggerPasskeymeAuth({
username: "[email protected]",
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 pagesinline: 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
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:
.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:
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
// 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
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
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
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
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
// 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
// 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
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
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
// 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:
// 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
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
