Skip to main content
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.
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.
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 below.

Prerequisites

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

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'.
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 }:
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:
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]);
}

authStateInfo Fields

FieldTypeDescription
userIdstring | nullThe user’s ID, if available at the current stage.
verificationUrlstring | nullPortal 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.
passkeyUrlstring | nullPortal URL for passkey login or creation. Open in the in-app browser.
passkeyKnownDeviceUrlstring | nullPortal URL for authorizing from a known device with passkey. Open in the in-app browser when present instead of passkeyUrl.
passwordUrlstring | nullPortal URL for password login or creation. Open in the in-app browser.
pinUrlstring | nullPortal URL for PIN login or creation. Open in the in-app browser.
isPasskeySupportedbooleanWhether the user’s device supports passkeys/WebAuthn.
hasPasskeybooleanWhether the user has a passkey configured.
hasPasswordbooleanWhether the user has a password configured.
hasPinbooleanWhether the user has a PIN configured.
isNewUserbooleanWhether this is a new signup flow.
passkeyIdstring | nullThe credential ID for the user’s passkey (signup only). Pass to registerPasskey() for native passkey registration on mobile.
passkeyHintsBiometricLocationHint[] | nullHints for known device detection.
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.

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

PhaseDescription
setting_upPara is initializing. Show a loading indicator.
auth_flowThe user is in the authentication flow. Refer to authPhase for details.
wallet_flowAuthentication succeeded; wallets are being set up. Refer to walletPhase for details.
authenticatedThe user is fully logged in with wallets ready.
guest_modeThe user is in guest mode (no full authentication).
logging_outA logout is in progress.
errorAn unrecoverable error occurred. Check snapshot.error.

authPhase — Authentication Flow Detail

PhaseDescriptionWhat to show
unauthenticatedNo auth in progress.Login / signup form.
authenticating_email_phoneEmail/phone auth initiated, routing in progress.Loading indicator.
authenticating_oauthStandard OAuth flow in progress.Loading indicator or “Waiting for provider…”.
authenticating_telegramTelegram auth in progress.Telegram popup/iframe.
authenticating_farcasterFarcaster auth in progress.Farcaster QR code.
processing_authenticationServer is processing the auth request.Loading indicator.
verifying_new_accountVerification code is being validated.Loading indicator.
awaiting_account_verificationNew 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_startPortal 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_sessionSession polling is active — waiting for the user to complete in the portal.”Completing setup…” loading state.
authenticatedAuth flow is complete. (Core may still be in wallet_flow.)Transition to authenticated UI, or show wallet loading indicator.
guest_modeThe user is in guest mode (no full authentication).Guest UI.
errorAuth flow failed. Check snapshot.error.Error message with retry option.
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).

walletPhase — Wallet Setup Detail

PhaseDescription
checking_wallet_stateChecking if wallets need to be created.
needs_walletsWallets need to be created for this user.
claiming_walletsClaiming pregenerated wallets that were created for this user before signup.
creating_walletsWallets are being created.
waiting_for_walletsWaiting for wallet creation or claiming to complete.
setting_up_after_loginPerforming post-login wallet setup (e.g. refreshing session, syncing wallet state).
wallets_readyWallets are created and ready to use.
no_wallets_neededUser already has wallets; nothing to create.
errorWallet creation failed. Check snapshot.error.

Method Reference

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.

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.
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:
const handleCancel = async () => {
  await para.logout();
  // All polling stops, state phases reset to unauthenticated
};

Handling Results

Next Steps