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

# Email & Phone Authentication

> Authenticate users with email or phone in React Native and Expo using Para's SDK

Para doesn't provide a pre-built modal for mobile -- you authenticate by calling SDK methods directly. This guide shows how to authenticate users with email or phone using `authenticateWithEmailOrPhone()`, which handles the entire auth flow in a single call.

<Warning>
  These methods are **long-running** -- they internally poll for session completion and wait for the user to finish interacting with the portal. Call them from a **provider or higher-order component** that will not unmount during the authentication flow. If the component unmounts while the method is running, the authentication will be interrupted.
</Warning>

<Note>
  You are responsible for **opening the portal URLs** that Para generates during authentication. Use `para.onStatePhaseChange()` to listen for these URLs and open them in the device browser. See [Handling Portal URLs](#handling-portal-urls) below.
</Note>

## Prerequisites

Before implementing authentication, ensure you have completed the basic Para setup for your React Native or Expo application.

<CardGroup cols={2}>
  <Card horizontal title="React Native Setup" imgUrl="/images/v3/framework-react-native.png" href="/v3/react-native/setup/react-native" description="Complete the basic Para setup for React Native before implementing custom auth" />

  <Card horizontal title="Expo Setup" imgUrl="/images/v3/framework-expo.png" href="/v3/react-native/setup/expo" description="Complete the basic Para setup for Expo before implementing custom auth" />
</CardGroup>

## Email / Phone Authentication

Use `para.authenticateWithEmailOrPhone()` to authenticate a user by email or phone. The method handles the complete flow: determining whether the user is new or returning, session polling, and wallet creation. Combine it with the state listener below to open portal URLs, and handle OTP input when `authPhase` is `'awaiting_account_verification'`.

```typescript theme={null}
import { useState } from "react";
import { para } from "../your-para-client";

function EmailAuthScreen() {
  const [isAuthActive, setIsAuthActive] = useState(false);

  // Use the state listener hook from below
  useParaAuthStateListener(isAuthActive);

  const handleEmailAuth = async (email: string) => {
    setIsAuthActive(true);

    try {
      const result = await para.authenticateWithEmailOrPhone({
        auth: { email },
      });

      if (result.hasCreatedWallets && result.recoverySecret) {
        // Non-basic-login new user — display or securely store the recovery secret
        console.log("Recovery secret:", result.recoverySecret);
      }

      // User is now fully authenticated
      console.log("Auth info:", result.authInfo);
      // Navigate to your authenticated screen
    } catch (error) {
      console.error("Authentication failed:", error);
    } finally {
      setIsAuthActive(false);
    }
  };

  // ... render your email input UI
}
```

For phone number authentication, pass `{ phone: '+1234567890' }` instead of `{ email }`:

```typescript theme={null}
const result = await para.authenticateWithEmailOrPhone({
  auth: { phone: `+${countryCode}${phoneNumber}` as `+${number}` },
});
```

## Handling Portal URLs

During authentication, Para's state machine emits portal URLs that the user must interact with (e.g. entering a verification code, creating a passkey, or entering a password). Since you're building a custom UI, you need to subscribe to state changes and open these URLs using the in-app browser.

Use `para.onStatePhaseChange()` to receive a `StateSnapshot` containing `authStateInfo` -- a flat object with all the URLs and flags you need:

<Tabs>
  <Tab title="React Native">
    ```typescript theme={null}
    import { useEffect, useRef } from "react";
    import { para } from "../your-para-client";
    import { InAppBrowser } from "react-native-inappbrowser-reborn";
    import type { StateSnapshot } from "@getpara/react-native-wallet";

    const APP_SCHEME = "your-app-scheme";

    function useParaAuthStateListener(isAuthActive: boolean) {
      const lastUrlRef = useRef<string | null>(null);

      useEffect(() => {
        if (!isAuthActive) return;

        const unsubscribe = para.onStatePhaseChange((snapshot: StateSnapshot) => {
          const { authStateInfo } = snapshot;

          // Verification URL (basic login verification)
          if (authStateInfo.verificationUrl && authStateInfo.verificationUrl !== lastUrlRef.current) {
            lastUrlRef.current = authStateInfo.verificationUrl;
            InAppBrowser.openAuth(authStateInfo.verificationUrl, APP_SCHEME, {
              ephemeralWebSession: false,
              showTitle: false,
            });
            return;
          }

          // Biometric / security URLs (passkey, password, PIN)
          const url =
            authStateInfo.passkeyKnownDeviceUrl ||
            authStateInfo.passkeyUrl ||
            authStateInfo.passwordUrl ||
            authStateInfo.pinUrl;
          if (url && url !== lastUrlRef.current) {
            lastUrlRef.current = url;
            InAppBrowser.openAuth(url, APP_SCHEME, {
              ephemeralWebSession: false,
              showTitle: false,
            });
          }
        });

        return () => {
          unsubscribe();
          lastUrlRef.current = null;
        };
      }, [isAuthActive]);
    }
    ```
  </Tab>

  <Tab title="Expo">
    ```typescript theme={null}
    import { useEffect, useRef } from "react";
    import { para } from "../your-para-client";
    import { openAuthSessionAsync } from "expo-web-browser";
    import type { StateSnapshot } from "@getpara/react-native-wallet";

    const APP_SCHEME = "your-app-scheme";

    function useParaAuthStateListener(isAuthActive: boolean) {
      const lastUrlRef = useRef<string | null>(null);

      useEffect(() => {
        if (!isAuthActive) return;

        const unsubscribe = para.onStatePhaseChange((snapshot: StateSnapshot) => {
          const { authStateInfo } = snapshot;

          // Verification URL (basic login verification)
          if (authStateInfo.verificationUrl && authStateInfo.verificationUrl !== lastUrlRef.current) {
            lastUrlRef.current = authStateInfo.verificationUrl;
            openAuthSessionAsync(authStateInfo.verificationUrl, APP_SCHEME, {
              preferEphemeralSession: false,
            });
            return;
          }

          // Biometric / security URLs (passkey, password, PIN)
          const url =
            authStateInfo.passkeyKnownDeviceUrl ||
            authStateInfo.passkeyUrl ||
            authStateInfo.passwordUrl ||
            authStateInfo.pinUrl;
          if (url && url !== lastUrlRef.current) {
            lastUrlRef.current = url;
            openAuthSessionAsync(url, APP_SCHEME, {
              preferEphemeralSession: false,
            });
          }
        });

        return () => {
          unsubscribe();
          lastUrlRef.current = null;
        };
      }, [isAuthActive]);
    }
    ```
  </Tab>
</Tabs>

## `authStateInfo` Fields

| Field                   | Type                              | Description                                                                                                                                                         |
| ----------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `userId`                | `string \| null`                  | The user's ID, if available at the current stage.                                                                                                                   |
| `verificationUrl`       | `string \| null`                  | Portal URL for basic login users to complete auth in the hosted portal. Only set during `awaiting_session_start` for basic login flows. Open in the in-app browser. |
| `passkeyUrl`            | `string \| null`                  | Portal URL for passkey login or creation. Open in the in-app browser.                                                                                               |
| `passkeyKnownDeviceUrl` | `string \| null`                  | Portal URL for authorizing from a known device with passkey. Open in the in-app browser when present instead of `passkeyUrl`.                                       |
| `passwordUrl`           | `string \| null`                  | Portal URL for password login or creation. Open in the in-app browser.                                                                                              |
| `pinUrl`                | `string \| null`                  | Portal URL for PIN login or creation. Open in the in-app browser.                                                                                                   |
| `isPasskeySupported`    | `boolean`                         | Whether the user's device supports passkeys/WebAuthn.                                                                                                               |
| `hasPasskey`            | `boolean`                         | Whether the user has a passkey configured.                                                                                                                          |
| `hasPassword`           | `boolean`                         | Whether the user has a password configured.                                                                                                                         |
| `hasPin`                | `boolean`                         | Whether the user has a PIN configured.                                                                                                                              |
| `isNewUser`             | `boolean`                         | Whether this is a new signup flow.                                                                                                                                  |
| `passkeyId`             | `string \| null`                  | The credential ID for the user's passkey (signup only). Pass to `registerPasskey()` for native passkey registration on mobile.                                      |
| `passkeyHints`          | `BiometricLocationHint[] \| null` | Hints for known device detection.                                                                                                                                   |

<Info>
  When `authPhase` is `'awaiting_account_verification'`, the user is a **non-basic-login new signup** who has been sent an OTP code via email or SMS. There is no URL to open -- you must show a code input field and call `para.verifyNewAccount({ verificationCode })`. If the user needs a new code, call `para.resendVerificationCode({ type: 'SIGNUP' })`. The simplified method is waiting for this step to complete before it proceeds. See the [full example above](#email--phone-authentication).
</Info>

## State Phase Reference

The `StateSnapshot` returned by `para.onStatePhaseChange()` contains three phase fields that tell you exactly where in the flow the user is. Use these to drive your UI.

### `corePhase` — Top-Level Lifecycle

| Phase           | Description                                                                             |
| --------------- | --------------------------------------------------------------------------------------- |
| `setting_up`    | Para is initializing. Show a loading indicator.                                         |
| `auth_flow`     | The user is in the authentication flow. Refer to `authPhase` for details.               |
| `wallet_flow`   | Authentication succeeded; wallets are being set up. Refer to `walletPhase` for details. |
| `authenticated` | The user is fully logged in with wallets ready.                                         |
| `guest_mode`    | The user is in guest mode (no full authentication).                                     |
| `logging_out`   | A logout is in progress.                                                                |
| `error`         | An unrecoverable error occurred. Check `snapshot.error`.                                |

### `authPhase` — Authentication Flow Detail

| Phase                           | Description                                                                                                                                                                                                             | What to show                                                                                |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `unauthenticated`               | No auth in progress.                                                                                                                                                                                                    | Login / signup form.                                                                        |
| `authenticating_email_phone`    | Email/phone auth initiated, routing in progress.                                                                                                                                                                        | Loading indicator.                                                                          |
| `authenticating_oauth`          | Standard OAuth flow in progress.                                                                                                                                                                                        | Loading indicator or "Waiting for provider...".                                             |
| `authenticating_telegram`       | Telegram auth in progress.                                                                                                                                                                                              | Telegram popup/iframe.                                                                      |
| `authenticating_farcaster`      | Farcaster auth in progress.                                                                                                                                                                                             | Farcaster QR code.                                                                          |
| `processing_authentication`     | Server is processing the auth request.                                                                                                                                                                                  | Loading indicator.                                                                          |
| `verifying_new_account`         | Verification code is being validated.                                                                                                                                                                                   | Loading indicator.                                                                          |
| `awaiting_account_verification` | New user (non-basic-login) needs to enter an OTP verification code sent to their email or phone. No URL to open — `authStateInfo` will not have a `verificationUrl`.                                                    | Show a code input field. Call `para.verifyNewAccount({ verificationCode })` when submitted. |
| `awaiting_session_start`        | Portal URLs are ready. For **basic login** users, `authStateInfo.verificationUrl` is set (user completes auth in the portal). For **passkey/password/PIN** users, `passkeyUrl`, `passwordUrl`, and/or `pinUrl` are set. | Open the appropriate URL. Passkey URLs **must** use a popup on web.                         |
| `waiting_for_session`           | Session polling is active — waiting for the user to complete in the portal.                                                                                                                                             | "Completing setup..." loading state.                                                        |
| `authenticated`                 | Auth flow is complete. (Core may still be in `wallet_flow`.)                                                                                                                                                            | Transition to authenticated UI, or show wallet loading indicator.                           |
| `guest_mode`                    | The user is in guest mode (no full authentication).                                                                                                                                                                     | Guest UI.                                                                                   |
| `error`                         | Auth flow failed. Check `snapshot.error`.                                                                                                                                                                               | Error message with retry option.                                                            |

<Info>
  **Basic login vs passkey/password/PIN:** Basic login users complete their entire authentication through a portal URL — the `verificationUrl` handles OTP entry, passkey creation, etc. in a single hosted flow. Passkey, password, and PIN users go through a two-step process where OTP verification happens in your app (via `awaiting_account_verification`) and biometric setup happens in the portal (via `awaiting_session_start`).
</Info>

### `walletPhase` — Wallet Setup Detail

| Phase                    | Description                                                                         |
| ------------------------ | ----------------------------------------------------------------------------------- |
| `checking_wallet_state`  | Checking if wallets need to be created.                                             |
| `needs_wallets`          | Wallets need to be created for this user.                                           |
| `claiming_wallets`       | Claiming pregenerated wallets that were created for this user before signup.        |
| `creating_wallets`       | Wallets are being created.                                                          |
| `waiting_for_wallets`    | Waiting for wallet creation or claiming to complete.                                |
| `setting_up_after_login` | Performing post-login wallet setup (e.g. refreshing session, syncing wallet state). |
| `wallets_ready`          | Wallets are created and ready to use.                                               |
| `no_wallets_needed`      | User already has wallets; nothing to create.                                        |
| `error`                  | Wallet creation failed. Check `snapshot.error`.                                     |

## Method Reference

<MethodDocs
  defaultExpanded={true}
  preventCollapse={true}
  name="authenticateWithEmailOrPhone()"
  description="Handles the entire email or phone authentication flow in a single call — signup or login, verification, session waiting, and wallet creation."
  async={true}
  parameters={[
{
  name: "auth",
  type: "VerifiedAuth",
  typeLink: "/v3/references/types/verifiedauth",
  required: true,
  description: "The user's email address or phone number, in the form `{ email: '...' } | { phone: '+1...' }`."
},
{
  name: "sessionPollingCallbacks",
  type: "PollingCallbacks",
  optional: true,
  description: "Callbacks fired while polling for session status. Includes `onPoll`, `onCancel`, and `isCanceled`."
},
{
  name: "portalTheme",
  type: "Theme",
  optional: true,
  description: "The theme to apply to generated URLs, if different from your configured theme."
},
{
  name: "useShortUrls",
  type: "boolean",
  optional: true,
  description: "Whether to shorten generated URLs. This may correct any issues with generated QR codes."
}
]}
  returns={{
type: "AuthenticateResponse",
description: "An object containing `authInfo` (user's authentication info), `hasCreatedWallets` (whether wallets were auto-created), and optionally `recoverySecret` (for new signups)."
}}
/>

<Note>
  This method must be paired with `para.onStatePhaseChange()` to handle portal URLs that appear during authentication (verification, passkey, password, PIN). See the guide for your platform for the full state listener pattern.
</Note>

## Cancelling Authentication

The method accepts polling callbacks with an `isCanceled` function. Return `true` from `isCanceled` to stop the polling loop -- for example, when the user dismisses the in-app browser or navigates away. The cancellation is clean: no error is thrown, and the optional `onCancel` callback is fired.

```typescript theme={null}
const result = await para.authenticateWithEmailOrPhone({
  auth: { email },
  sessionPollingCallbacks: {
    isCanceled: () => userDismissedBrowser,
    onCancel: () => {
      console.log("User canceled authentication");
    },
  },
});
```

Calling `para.logout()` also cancels all active polling and resets the state phases back to `unauthenticated`. This is useful for implementing a "Cancel" button that fully resets the auth flow:

```typescript theme={null}
const handleCancel = async () => {
  await para.logout();
  // All polling stops, state phases reset to unauthenticated
};
```

## Handling Results

<MethodDocs
  defaultExpanded={true}
  preventCollapse={true}
  name="AuthenticateResponse"
  tag="Type"
  description="Response type returned by `authenticateWithEmailOrPhone()` and `authenticateWithOAuth()` when the auth flow completes successfully."
  parameters={[
{
  name: "authInfo",
  type: "CoreAuthInfo",
  required: true,
  description: "Contains the user's primary authentication information such as `email`, `phone`, `userId`, and any auth extras."
},
{
  name: "hasCreatedWallets",
  type: "boolean",
  required: true,
  description: "`true` if the user is new and wallets were auto-created during signup. `false` for returning users."
},
{
  name: "recoverySecret",
  type: "string",
  optional: true,
  description: "Present only for **non-basic-login** new signups when wallets are created. Basic login users do not receive a recovery secret. This should be displayed to the user or stored securely — it cannot be retrieved again."
}
]}
/>

## Next Steps

<CardGroup cols={3}>
  <Card title="Add Social Login" imgUrl="/images/v3/custom-ui-social.png" href="/v3/react-native/guides/add-social-login" description="Authenticate users with Google, Apple, Discord, and more." />

  <Card title="Add Passkeys" imgUrl="/images/v3/general-passwords.png" href="/v3/react-native/guides/add-passkeys" description="Add native biometric authentication for enhanced security." />

  <Card title="Add Password or PIN" imgUrl="/images/v3/general-passwords.png" href="/v3/react-native/guides/add-password-pin" description="Add password or PIN-based authentication." />
</CardGroup>
