> ## Documentation Index
> Fetch the complete documentation index at: https://docs.socket.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# React Authentication Example with SocketFi Passkeys

> A complete, production-ready React authentication flow using SocketFi passkeys, session persistence, protected routes, and backend token verification.

Building authentication with SocketFi means your users get a passkey-powered, seed-phrase-free login experience, and you get a fully typed session object with a wallet address and an access token ready to send to your backend. This example walks you through every file you need — from SDK initialization to protected routes — so you can drop a working auth system into any React project.

## Project structure

Your authentication module lives under `src/auth/`. Keep the SocketFi client in its own file so it's instantiated exactly once and imported wherever you need it.

```text theme={null}
src/
├── socketfi/
│   └── client.ts          # SDK singleton
├── auth/
│   ├── AuthContext.tsx    # Context shape + createContext
│   ├── AuthProvider.tsx   # State, login, logout, session restore
│   └── useAuth.ts         # Convenience hook
├── routes/
│   └── ProtectedRoute.tsx # Route guard
└── pages/
    └── LoginPage.tsx      # Login button component
```

## Installation

```bash theme={null}
npm install @socketfi/react
```

Set your client ID in an environment variable — never hard-code it.

```bash theme={null}
# .env
VITE_SOCKETFI_CLIENT_ID=your_client_id_here
```

## Step-by-step files

<Steps>
  ### client.ts — SDK initialization

  Create the SocketFi singleton once and export it. Every other module imports from here.

  ```typescript src/socketfi/client.ts theme={null}
  import { SocketFi } from "@socketfi/react";

  export const socketfi = new SocketFi({
    clientId: import.meta.env.VITE_SOCKETFI_CLIENT_ID,
    network: "TESTNET", // Switch to "MAINNET" for production
  });
  ```

  <Note>
    The `network` option controls which Soroban network your smart wallets operate on. Use `"TESTNET"` during development and `"MAINNET"` when you go live.
  </Note>

  ### AuthContext.tsx — Context definition

  Define the shape of your auth context separately so `AuthProvider` and consumers share the same type.

  ```typescript src/auth/AuthContext.tsx theme={null}
  import { createContext } from "react";

  export interface AuthSession {
    socketfiAccessToken: string;
    userProfile: {
      id: string;
      wallet: string;
    };
  }

  export interface AuthContextValue {
    loading: boolean;
    authenticated: boolean;
    session: AuthSession | null;
    login: () => Promise<void>;
    logout: () => void;
  }

  export const AuthContext = createContext<AuthContextValue | undefined>(
    undefined
  );
  ```

  ### AuthProvider.tsx — Provider with session restore

  The provider restores any persisted session on mount, exposes `login` and `logout`, and stores the session in `localStorage` so users stay signed in across page refreshes.

  ```typescript src/auth/AuthProvider.tsx theme={null}
  import { useEffect, useState } from "react";
  import type { AuthSession, AuthContextValue } from "./AuthContext";
  import { AuthContext } from "./AuthContext";
  import { socketfi } from "../socketfi/client";

  const STORAGE_KEY = "socketfi_session";

  export function AuthProvider({ children }: { children: React.ReactNode }) {
    const [loading, setLoading] = useState(true);
    const [session, setSession] = useState<AuthSession | null>(null);

    // Restore session on app startup
    useEffect(() => {
      try {
        const stored = localStorage.getItem(STORAGE_KEY);
        if (stored) {
          setSession(JSON.parse(stored) as AuthSession);
        }
      } catch {
        localStorage.removeItem(STORAGE_KEY);
      } finally {
        setLoading(false);
      }
    }, []);

    const login = async () => {
      const result = await socketfi.authenticate();
      localStorage.setItem(STORAGE_KEY, JSON.stringify(result));
      setSession(result as AuthSession);
    };

    const logout = () => {
      localStorage.removeItem(STORAGE_KEY);
      setSession(null);
    };

    const value: AuthContextValue = {
      loading,
      authenticated: !!session,
      session,
      login,
      logout,
    };

    return (
      <AuthContext.Provider value={value}>
        {children}
      </AuthContext.Provider>
    );
  }
  ```

  <Tip>
    On mobile (React Native), swap `localStorage` for `expo-secure-store` or an equivalent secure device storage library.
  </Tip>

  ### useAuth.ts — Convenience hook

  Wrap `useContext` so consumers get a friendly error if they're used outside the provider.

  ```typescript src/auth/useAuth.ts theme={null}
  import { useContext } from "react";
  import { AuthContext } from "./AuthContext";

  export function useAuth() {
    const context = useContext(AuthContext);
    if (!context) {
      throw new Error("useAuth must be used within <AuthProvider>");
    }
    return context;
  }
  ```

  ### LoginPage.tsx — Login button component

  A minimal login page. Disable the button while `authenticate()` is in flight to prevent double-submissions.

  ```typescript src/pages/LoginPage.tsx theme={null}
  import { useState } from "react";
  import { useNavigate } from "react-router-dom";
  import { useAuth } from "../auth/useAuth";

  export default function LoginPage() {
    const { login } = useAuth();
    const navigate = useNavigate();
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);

    const handleLogin = async () => {
      try {
        setLoading(true);
        setError(null);
        await login();
        navigate("/");
      } catch (err: unknown) {
        const code = (err as { code?: string })?.code;
        // USER_CANCELLED is a normal user action — don't treat it as an error
        if (code !== "USER_CANCELLED") {
          setError("Authentication failed. Please try again.");
        }
      } finally {
        setLoading(false);
      }
    };

    return (
      <main>
        <h1>Welcome to SocketPay</h1>
        <p>Sign in with your passkey to access your wallet.</p>
        {error && <p role="alert">{error}</p>}
        <button disabled={loading} onClick={handleLogin}>
          {loading ? "Connecting…" : "Continue with passkey"}
        </button>
      </main>
    );
  }
  ```

  ### ProtectedRoute.tsx — Route guard

  Redirect unauthenticated users to `/login` while the session loads.

  ```typescript src/routes/ProtectedRoute.tsx theme={null}
  import { Navigate } from "react-router-dom";
  import { useAuth } from "../auth/useAuth";

  export function ProtectedRoute({ children }: { children: React.ReactNode }) {
    const { authenticated, loading } = useAuth();

    if (loading) {
      return <p>Loading…</p>;
    }

    if (!authenticated) {
      return <Navigate to="/login" replace />;
    }

    return <>{children}</>;
  }
  ```
</Steps>

## Wire everything together

Wrap your app in `<AuthProvider>` at the root so every component can call `useAuth()`.

```typescript src/main.tsx theme={null}
import React from "react";
import ReactDOM from "react-dom/client";
import { AuthProvider } from "./auth/AuthProvider";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>
);
```

```typescript src/App.tsx theme={null}
import { BrowserRouter, Routes, Route } from "react-router-dom";
import LoginPage from "./pages/LoginPage";
import DashboardPage from "./pages/DashboardPage";
import { ProtectedRoute } from "./routes/ProtectedRoute";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        <Route
          path="/"
          element={
            <ProtectedRoute>
              <DashboardPage />
            </ProtectedRoute>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}
```

## Authentication flow

```text theme={null}
User clicks "Continue with passkey"
       ↓
socketfi.authenticate() opens the hosted flow
       ↓
User approves with Face ID / Touch ID / Windows Hello
       ↓
SocketFi returns AuthSession { socketfiAccessToken, userProfile }
       ↓
Session stored in localStorage
       ↓
User redirected to protected route
```

## Session persistence flow

```text theme={null}
App starts
       ↓
AuthProvider mounts → reads localStorage
       ↓
Existing session found → setSession(parsed)
       ↓
User is immediately authenticated (no second login prompt)
```

## Accessing wallet info

After authentication, the wallet address is available anywhere inside `<AuthProvider>`:

```typescript theme={null}
const { session } = useAuth();
const walletAddress = session?.userProfile?.wallet;
```

## Sending the token to your backend

Attach `socketfiAccessToken` as a Bearer token on every authenticated API request:

```typescript theme={null}
const { session } = useAuth();

const response = await fetch("/api/me", {
  headers: {
    Authorization: `Bearer ${session?.socketfiAccessToken}`,
  },
});
```

## Backend token verification

Your Express (or other) server verifies the token with `@socketfi/server` before trusting any request. See the [Server Verification](/examples/server-verification) example for a complete implementation.

```typescript theme={null}
import { verifyAuth } from "@socketfi/server";

const result = await verifyAuth(accessToken);
// result.valid, result.user, result.wallet
```

<Warning>
  Never trust wallet addresses or user IDs sent directly from the client. Always verify the access token server-side with `verifyAuth()` and derive identity from the verified result.
</Warning>

## Logout flow

```text theme={null}
User clicks "Logout"
       ↓
localStorage.removeItem("socketfi_session")
       ↓
setSession(null)
       ↓
ProtectedRoute redirects to /login
```

Logout only clears local session state — the user's wallet and assets are completely unaffected.

## Production checklist

<CardGroup cols={2}>
  <Card title="Session security" icon="shield">
    Store sessions in `localStorage` for web or secure device storage for mobile. Never store tokens in memory-only state.
  </Card>

  <Card title="Error handling" icon="triangle-exclamation">
    Catch `USER_CANCELLED` separately and don't show it as an error. All other failures should surface a retry prompt.
  </Card>

  <Card title="Token verification" icon="server">
    Every protected backend endpoint must call `verifyAuth()` — never rely on client-provided identity claims.
  </Card>

  <Card title="HTTPS" icon="lock">
    WebAuthn / passkeys require a secure context. HTTPS is mandatory in production.
  </Card>
</CardGroup>
