> ## 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 Native SDK Authentication Guide — SocketFi

> Authenticate users via passkey in a hosted browser, persist sessions with SecureStore, and restore them on app launch with a reusable AuthProvider hook.

Authentication in the React Native SDK follows the same API as the React SDK — you call `authenticate()` and receive a session — but the mechanics are different. Instead of a popup, the SDK opens a hosted browser session using `expo-web-browser`. After the user completes passkey sign-in or registration, SocketFi deep-links back to your app and the Promise resolves with the session.

## How authentication works

<Steps>
  <Step title="authenticate() opens the hosted browser">
    The SDK calls `WebBrowser.openAuthSessionAsync()` under the hood, launching the SocketFi authentication screen inside an in-app browser.
  </Step>

  <Step title="User authenticates with a passkey">
    New users register a device passkey. Returning users verify their existing passkey. The SDK handles both automatically.
  </Step>

  <Step title="Wallet resolved or created">
    For returning users, the associated Stellar smart wallet is resolved. For new users, a new embedded smart wallet is created and activated.
  </Step>

  <Step title="Deep link returns the user to your app">
    After the flow completes, SocketFi redirects the browser to `yourscheme://socketfi/auth/success` and the in-app browser closes.
  </Step>

  <Step title="Session returned">
    The `authenticate()` Promise resolves with the `Session` object containing the user profile and access token.
  </Step>
</Steps>

## The Session object

```typescript theme={null}
type Session = {
  userProfile: {
    id: string;
  };
  socketfiAccessToken: string;
};
```

Store `userProfile` for identifying the user in your UI and `socketfiAccessToken` for authenticated SDK calls. Do not store sensitive wallet internals on the client.

## Persisting sessions with SecureStore

Because React Native apps can be killed and relaunched, you should persist the session token to device storage so users don't need to re-authenticate every time they open the app. Use `expo-secure-store` for encrypted storage on both iOS and Android.

```bash theme={null}
npx expo install expo-secure-store
```

```typescript lib/session.ts theme={null}
import * as SecureStore from "expo-secure-store";

const SESSION_KEY = "socketfi_session";

type StoredSession = {
  userProfile: { id: string };
  socketfiAccessToken: string;
};

export async function saveSession(session: StoredSession): Promise<void> {
  await SecureStore.setItemAsync(SESSION_KEY, JSON.stringify(session));
}

export async function loadSession(): Promise<StoredSession | null> {
  const raw = await SecureStore.getItemAsync(SESSION_KEY);
  if (!raw) return null;
  try {
    return JSON.parse(raw) as StoredSession;
  } catch {
    return null;
  }
}

export async function clearSession(): Promise<void> {
  await SecureStore.deleteItemAsync(SESSION_KEY);
}
```

## AuthProvider + useSocketFiAuth hook

The pattern below gives every component in your app access to the current session, a `login` function, and a `logout` function. It also restores the persisted session automatically when the app launches.

```typescript lib/AuthProvider.tsx theme={null}
import {
  createContext,
  useContext,
  useState,
  useEffect,
  type ReactNode,
} from "react";
import { socketfi } from "./socketfi";
import { saveSession, loadSession, clearSession } from "./session";

type UserProfile = {
  id: string;
};

type AuthContextValue = {
  user: UserProfile | null;
  accessToken: string | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  login: () => Promise<void>;
  logout: () => Promise<void>;
};

const AuthContext = createContext<AuthContextValue | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<UserProfile | null>(null);
  const [accessToken, setAccessToken] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  // Restore session from secure storage on app launch
  useEffect(() => {
    async function restoreSession() {
      try {
        const stored = await loadSession();
        if (stored) {
          setUser(stored.userProfile);
          setAccessToken(stored.socketfiAccessToken);
        }
      } catch (error) {
        console.warn("Could not restore session:", error);
      } finally {
        setIsLoading(false);
      }
    }

    restoreSession();
  }, []);

  async function login() {
    const session = await socketfi.authenticate();
    setUser(session.userProfile);
    setAccessToken(session.socketfiAccessToken);
    await saveSession(session);
  }

  async function logout() {
    setUser(null);
    setAccessToken(null);
    await clearSession();
  }

  return (
    <AuthContext.Provider
      value={{
        user,
        accessToken,
        isAuthenticated: Boolean(accessToken),
        isLoading,
        login,
        logout,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
}

export function useSocketFiAuth(): AuthContextValue {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useSocketFiAuth must be used within an AuthProvider");
  }
  return context;
}
```

## Wiring AuthProvider into your app

Wrap your root component with `AuthProvider` so the hook is available everywhere:

```typescript App.tsx theme={null}
import { useEffect } from "react";
import * as Linking from "expo-linking";
import { AuthProvider } from "./lib/AuthProvider";
import { RootNavigator } from "./navigation/RootNavigator";

export default function App() {
  // Deep link listener (see deep linking guide)
  useEffect(() => {
    const subscription = Linking.addEventListener("url", ({ url }) => {
      console.log("Deep link received:", url);
    });
    return () => subscription.remove();
  }, []);

  return (
    <AuthProvider>
      <RootNavigator />
    </AuthProvider>
  );
}
```

## Login button example

```typescript components/LoginButton.tsx theme={null}
import { Button, View, Text } from "react-native";
import { useState } from "react";
import { useSocketFiAuth } from "../lib/AuthProvider";

export function LoginButton() {
  const { login } = useSocketFiAuth();
  const [error, setError] = useState<string | null>(null);

  async function handleLogin() {
    setError(null);
    try {
      await login();
    } catch (err: any) {
      if (err?.code !== "USER_CANCELLED") {
        setError("Authentication failed. Please try again.");
        console.error(err);
      }
    }
  }

  return (
    <View>
      {error && <Text accessibilityRole="alert">{error}</Text>}
      <Button title="Continue with SocketFi" onPress={handleLogin} />
    </View>
  );
}
```

## Handling session restoration on app launch

The `isLoading` flag from the context tells you whether the persisted session check is still in progress. Use it to show a splash screen or loading indicator before rendering authenticated content:

```typescript navigation/RootNavigator.tsx theme={null}
import { View, ActivityIndicator } from "react-native";
import { useSocketFiAuth } from "../lib/AuthProvider";
import { AuthStack } from "./AuthStack";
import { AppStack } from "./AppStack";

export function RootNavigator() {
  const { isAuthenticated, isLoading } = useSocketFiAuth();

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

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

## Error handling

Wrap all `authenticate()` calls in `try/catch` and handle the codes your users are most likely to see:

```typescript theme={null}
async function handleLogin() {
  try {
    await login();
  } catch (error: any) {
    switch (error?.code) {
      case "USER_CANCELLED":
        // User closed the browser intentionally — no error message needed
        break;
      case "INVALID_CLIENT_ID":
        Alert.alert(
          "Configuration Error",
          "There is a problem with the app configuration. Please contact support."
        );
        break;
      case "AUTHENTICATION_FAILED":
        Alert.alert(
          "Authentication Failed",
          "We couldn't verify your identity. Please try again on a supported device."
        );
        break;
      case "TOKEN_EXPIRED":
        // Session expired — re-authenticate
        Alert.alert("Session Expired", "Please sign in again to continue.");
        break;
      default:
        Alert.alert("Error", "Something went wrong. Please try again.");
        console.error("Unexpected auth error:", error);
    }
  }
}
```

### Error codes reference

| Code                    | Cause                                                                    | Recommended action                                                      |
| ----------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| `USER_CANCELLED`        | User closed the browser before finishing                                 | Reset UI silently; no error needed                                      |
| `INVALID_CLIENT_ID`     | `clientId` is wrong or inactive for the selected network                 | Verify the value in your `.env` file and the Developer Portal           |
| `AUTHENTICATION_FAILED` | Passkey verification failed on the device                                | Ask the user to retry on a supported device                             |
| `TOKEN_EXPIRED`         | The SocketFi session token has expired                                   | Call `authenticate()` again to obtain a fresh session                   |
| `POPUP_BLOCKED`         | Not applicable on mobile — the in-app browser is used instead of a popup | Ensure `expo-web-browser` is installed and the app scheme is configured |

<Tip>
  If you see `DEEP_LINK_NOT_CONFIGURED` errors during testing, make sure you are running a development build (`npx expo run:ios` / `npx expo run:android`) rather than Expo Go. Expo Go uses its own URL scheme and cannot intercept your custom scheme.
</Tip>
