> ## 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.

# Authenticate Users with SocketFi Passkey Authentication

> Complete guide to passkey authentication, session management, backend token verification, protected routes, and logout in SocketFi apps.

Authentication is the gateway to every SocketFi capability. Before a user can view their wallet, request a transaction, or interact with any on-chain contract, they must first authenticate. SocketFi handles this through a hosted, passwordless flow built on passkeys — the same FIDO2 standard used by Apple, Google, and Microsoft. A single `authenticate()` call covers both new user registration and returning user sign-in; the SDK detects which flow to run automatically.

This guide covers the full authentication lifecycle: passkey registration vs. sign-in, session storage, backend token verification, protected routes, session expiration, re-authentication, and logout.

## How authentication works

When you call `socketfi.authenticate()`, the SDK opens the SocketFi hosted authentication flow. For a **new user**, the flow creates a passkey on their device and provisions a smart wallet on Stellar. For a **returning user**, the flow prompts for their existing passkey and creates a fresh session. In both cases, your app receives a `Session` object containing an access token and wallet information.

```text theme={null}
User calls authenticate()
        ↓
SocketFi hosted flow opens
        ↓
Passkey prompt (device biometrics / security key)
        ↓
Session returned to your app
        ↓
User accesses wallet
```

<Note>
  Authentication proves **identity** — it does not authorize individual transactions. Every state-changing contract call goes through a separate approval step when you call `requestTransaction()`.
</Note>

## Calling authenticate()

The call signature is the same for both registration and sign-in:

```typescript theme={null}
const session = await socketfi.authenticate();
```

The returned `Session` object looks like this:

```typescript theme={null}
type Session = {
  socketfiAccessToken: string; // JWT — verify this server-side
  userProfile: {
    id: string;          // SocketFi user identifier
    wallet: string;      // Stellar smart wallet address (e.g. "CDXX…")
  };
};
```

## Session storage and management

After authentication succeeds, persist the session so users stay logged in across page reloads or app restarts.

<Tabs>
  <Tab title="React (web)">
    Store the session in `localStorage` and rehydrate it on mount:

    ```typescript src/auth/provider.tsx theme={null}
    import { useState, useEffect, useCallback, ReactNode } from "react";
    import { socketfi } from "../socketfi/client";
    import { AuthContext, Session } from "./context";

    const SESSION_KEY = "socketfi_session";

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

      useEffect(() => {
        const stored = localStorage.getItem(SESSION_KEY);
        if (stored) {
          try {
            setSession(JSON.parse(stored));
          } catch {
            localStorage.removeItem(SESSION_KEY);
          }
        }
        setLoading(false);
      }, []);

      const login = useCallback(async () => {
        setLoading(true);
        try {
          const result = await socketfi.authenticate();
          setSession(result as Session);
          localStorage.setItem(SESSION_KEY, JSON.stringify(result));
        } finally {
          setLoading(false);
        }
      }, []);

      const logout = useCallback(() => {
        setSession(null);
        localStorage.removeItem(SESSION_KEY);
      }, []);

      return (
        <AuthContext.Provider
          value={{ loading, authenticated: !!session, session, login, logout }}
        >
          {children}
        </AuthContext.Provider>
      );
    }
    ```
  </Tab>

  <Tab title="React Native">
    Use `expo-secure-store` — it encrypts data in the device keychain, unlike `AsyncStorage` which stores data in plain text.

    ```typescript src/auth/provider.tsx theme={null}
    import { useState, useEffect, useCallback, ReactNode } from "react";
    import * as SecureStore from "expo-secure-store";
    import { socketfi } from "../socketfi/client";
    import { AuthContext, Session } from "./context";

    const SESSION_KEY = "socketfi_session";

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

      useEffect(() => {
        SecureStore.getItemAsync(SESSION_KEY).then((stored) => {
          if (stored) {
            try { setSession(JSON.parse(stored)); }
            catch { SecureStore.deleteItemAsync(SESSION_KEY); }
          }
          setLoading(false);
        });
      }, []);

      const login = useCallback(async () => {
        setLoading(true);
        try {
          const result = await socketfi.authenticate();
          setSession(result as Session);
          await SecureStore.setItemAsync(SESSION_KEY, JSON.stringify(result));
        } finally {
          setLoading(false);
        }
      }, []);

      const logout = useCallback(async () => {
        setSession(null);
        await SecureStore.deleteItemAsync(SESSION_KEY);
      }, []);

      return (
        <AuthContext.Provider
          value={{ loading, authenticated: !!session, session, login, logout }}
        >
          {children}
        </AuthContext.Provider>
      );
    }
    ```
  </Tab>
</Tabs>

## Backend token verification

Never rely on client-side session state to grant access to backend resources. Always verify `socketfiAccessToken` server-side using the `@socketfi/server` package.

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

The `verifyAuth()` function validates the token signature and returns the verified user and wallet:

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

const result = await verifyAuth(token);
// result.valid         → boolean
// result.user.id       → SocketFi user identifier
// result.wallet.address → Stellar wallet address
```

### Express middleware example

```typescript src/server/middleware/requireAuth.ts theme={null}
import { Request, Response, NextFunction } from "express";
import { verifyAuth } from "@socketfi/server";

export async function requireAuth(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const header = req.headers.authorization;
  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing authorization header" });
  }

  const token = header.slice(7);

  try {
    const result = await verifyAuth(token);

    if (!result.valid) {
      return res.status(401).json({ error: "Invalid or expired token" });
    }

    // Attach verified identity so downstream handlers can use it safely
    (req as any).socketfi = {
      userId: result.user.id,
      walletAddress: result.wallet.address,
    };

    next();
  } catch {
    return res.status(401).json({ error: "Token verification failed" });
  }
}
```

Apply it to protected routes:

```typescript src/server/routes/wallet.ts theme={null}
import { Router } from "express";
import { requireAuth } from "../middleware/requireAuth";

const router = Router();

router.get("/balance", requireAuth, async (req, res) => {
  const { walletAddress } = (req as any).socketfi;
  // fetch balance for walletAddress …
  res.json({ walletAddress, balance: "1000" });
});

export default router;
```

## Protected routes

### React (web)

Wrap sensitive routes in a guard component that redirects unauthenticated users to your login page:

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

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

  if (loading) {
    return (
      <div style={{ display: "flex", justifyContent: "center", padding: 48 }}>
        <span>Loading…</span>
      </div>
    );
  }

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

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

### React Native

Use a navigator that conditionally renders the authenticated or unauthenticated stack:

```typescript src/navigation/RootNavigator.tsx theme={null}
import { useAuth } from "../auth/context";
import { AuthStack } from "./AuthStack";
import { AppStack } from "./AppStack";

export function RootNavigator() {
  const { loading, authenticated } = useAuth();

  if (loading) return null; // or a splash screen

  return authenticated ? <AppStack /> : <AuthStack />;
}
```

## Handling session expiration and re-authentication

Sessions expire after a period of inactivity. When an API call returns `401`, catch it and trigger re-authentication:

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

export async function authenticatedFetch(
  url: string,
  options?: RequestInit
): Promise<Response> {
  const token = getStoredToken(); // read from your session store

  const res = await fetch(url, {
    ...options,
    headers: {
      ...options?.headers,
      Authorization: `Bearer ${token}`,
    },
  });

  if (res.status === 401) {
    // Session expired — re-authenticate transparently
    const session = await socketfi.authenticate();
    storeSession(session); // persist the refreshed session

    // Retry the original request with the new token
    return fetch(url, {
      ...options,
      headers: {
        ...options?.headers,
        Authorization: `Bearer ${session.socketfiAccessToken}`,
      },
    });
  }

  return res;
}
```

## Logout

Logout removes the local session state. It does **not** delete the user's wallet, revoke the passkey, or affect any on-chain assets.

<Tabs>
  <Tab title="React (web)">
    ```typescript theme={null}
    const logout = () => {
      setSession(null);
      localStorage.removeItem("socketfi_session");
    };
    ```
  </Tab>

  <Tab title="React Native">
    ```typescript theme={null}
    const logout = async () => {
      setSession(null);
      await SecureStore.deleteItemAsync("socketfi_session");
    };
    ```
  </Tab>
</Tabs>

After logout, redirect the user to your login screen and reset any component state that depends on the session.

## Error handling for auth failures

Wrap every `authenticate()` call in `try/catch`. The most common errors are:

| Error code            | Cause                                                         | Recommended response                                  |
| --------------------- | ------------------------------------------------------------- | ----------------------------------------------------- |
| `USER_CANCELLED`      | User dismissed the passkey prompt                             | Show a gentle retry message                           |
| `POPUP_BLOCKED`       | Browser blocked the SocketFi popup                            | Advise the user to allow popups for your domain       |
| `INVALID_CLIENT_ID`   | The `clientId` passed to `new SocketFi()` is wrong or missing | Check your environment variable and Dashboard project |
| Passkey rejected      | Biometric check failed                                        | Ask the user to try again                             |
| Authentication failed | Network or server issue                                       | Show an error with a retry button                     |
| Credential missing    | Passkey deleted from device                                   | Prompt the user to start account recovery             |

```typescript theme={null}
const handleLogin = async () => {
  setLoading(true);
  setError(null);
  try {
    await login();
  } catch (err: unknown) {
    if (err instanceof Error) {
      if (err.message === "USER_CANCELLED") {
        // User dismissed — don't show an alarming error
        setError("Sign-in cancelled. Tap the button to try again.");
      } else if (err.message === "POPUP_BLOCKED") {
        setError("Sign-in popup was blocked. Please allow popups for this site and try again.");
      } else if (err.message === "INVALID_CLIENT_ID") {
        setError("Configuration error. Please contact support.");
      } else {
        setError("Sign-in failed. Please try again.");
      }
    }
  } finally {
    setLoading(false);
  }
};
```

You can also configure a global error handler on the client instance to capture all SDK errors in one place:

```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",
  onError(error) {
    // Send to Sentry, Datadog, or your error tracking tool
    reportError(error);
  },
});
```

## Production checklist

* ✅ Verify `socketfiAccessToken` server-side with `@socketfi/server` before granting API access
* ✅ Store tokens in encrypted storage (`expo-secure-store` on mobile, `sessionStorage` for stricter web security)
* ✅ Handle session expiration and re-authenticate transparently
* ✅ Protect all sensitive routes with an auth guard
* ✅ Show loading indicators during the passkey prompt and session restore
* ✅ Monitor authentication failures in your observability tool
