Skip to main content
By default, Privy caches the MFA token after verification and skips re-prompting until it expires. Apps that need fresh verification on every launch can clear the cached token whenever the app moves to the background. A trading app, for example, might treat each app open as a new security session. This recipe uses React Native’s AppState API and Privy’s clear method from useMfa to tie MFA verification to app session lifecycle.

How it works

  1. The user opens the app, authenticates, and proceeds normally. Wallet actions trigger MFA as usual.
  2. When the user backgrounds the app, clear() invalidates the cached MFA token.
  3. When the user returns and performs a wallet action, Privy finds no valid token and prompts for fresh verification.

Implementation

Register an AppState listener in a component near the root of your app. When the app leaves the active state — backgrounded or inactive — clear the MFA token:
import {useEffect} from 'react';
import {AppState} from 'react-native';
import {useMfa} from '@privy-io/expo';

export default function AppSessionMfaGuard() {
  const {clear} = useMfa();

  useEffect(() => {
    const subscription = AppState.addEventListener('change', (nextState) => {
      if (nextState !== 'active') {
        clear();
      }
    });

    return () => subscription.remove();
  }, [clear]);

  return null;
}
Render <AppSessionMfaGuard /> inside PrivyProvider, near the root of your app:
import {PrivyProvider} from '@privy-io/expo';
import AppSessionMfaGuard from './AppSessionMfaGuard';

export default function App() {
  return (
    <PrivyProvider appId="your-privy-app-id" clientId="your-privy-client-id">
      <AppSessionMfaGuard />
      {/* rest of your app */}
    </PrivyProvider>
  );
}

Proactively prompting on app resume

Wallet actions naturally re-trigger MFA after clear(). To prompt immediately on foreground instead, call prompt() when the app returns to active:
import {useEffect, useRef} from 'react';
import {AppState, AppStateStatus} from 'react-native';
import {useMfa} from '@privy-io/expo';

export default function AppSessionMfaGuard() {
  const {clear, prompt} = useMfa();
  const appState = useRef<AppStateStatus>(AppState.currentState);

  useEffect(() => {
    const subscription = AppState.addEventListener('change', async (nextState) => {
      const previousState = appState.current;
      appState.current = nextState;

      if (nextState !== 'active') {
        // App is going to the background — clear the cached MFA token
        clear();
      } else if (previousState !== 'active' && nextState === 'active') {
        // App is returning to the foreground — proactively prompt for MFA
        await prompt();
      }
    });

    return () => subscription.remove();
  }, [clear, prompt]);

  return null;
}
prompt() no-ops when the user has no MFA methods enrolled. Because clear() runs on every background event, there is never a cached token on resume. The user is always prompted.

Extending the cache duration

By default, MFA tokens stay valid for 15 minutes. For a session-based approach, a longer cache prevents mid-session expiry. clear() then handles explicit invalidation on background. Configure the MFA token duration in the Privy Dashboard. Setting a value of several hours ensures no mid-session re-prompts while the user is active.

iOS background constraints

On iOS, apps that move to the background have limited time before execution suspends. clear() makes no network requests and operates only on local state. It completes reliably within iOS background execution limits.