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
- The user opens the app, authenticates, and proceeds normally. Wallet actions trigger MFA as usual.
- When the user backgrounds the app,
clear() invalidates the cached MFA token.
- 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.