diff --git a/example/README.md b/example/README.md
index ed75be153..fb98d9ff6 100644
--- a/example/README.md
+++ b/example/README.md
@@ -13,6 +13,28 @@ To run the example application inside the repository, follow these steps:
The application will be built and launched on the specified platform, allowing you to interact with it.
+To run the web example, run `yarn web` from the `example` directory and open the served URL.
+
+### Layout
+
+The app has no navigation. `App.tsx` (native) renders a single sectioned
+`ScrollView`, one section per SDK feature group: Web Auth, Credentials Manager,
+Direct Auth API (native only), Passwordless (native only), MFA, My Account,
+Passkeys, and Advanced Tokens. `App.web.tsx` is a single file covering the
+web-supported features only, with web-specific methods labelled inline.
+
+Each native feature has two files under `src/features/`:
+
+- `FeatureHooks.tsx` — the `useAuth0()` hooks approach. These are the files the
+ app actually imports and renders.
+- `FeatureClass.tsx` — the same feature written against the `Auth0` class
+ instance (`src/shared/api.ts`), kept as unused side-by-side reference. Nothing
+ imports these; they exist to show the class API next to the hooks API.
+
+Platform-specific methods carry the platform in their label (e.g.
+`resumeSession (Android)`, `cancelWebAuth (iOS)`, `saveCredentials (Native only)`).
+There is no custom styling — only default React Native components with spacing.
+
### To run on different Auth0 Application
1. Change the `clientId` and `domain` value in `example/src/auth0-configuration.js`
diff --git a/example/package.json b/example/package.json
index b5b840d91..c1d55e1d8 100644
--- a/example/package.json
+++ b/example/package.json
@@ -12,14 +12,9 @@
"build:ios": "react-native build-ios --mode Debug"
},
"dependencies": {
- "@react-navigation/bottom-tabs": "^7.18.15",
- "@react-navigation/native": "^7.3.15",
- "@react-navigation/stack": "^7.10.20",
"react": "19.2.8",
"react-native": "0.86.2",
- "react-native-gesture-handler": "^3.0.2",
"react-native-safe-area-context": "^5.8.1",
- "react-native-screens": "^4.27.0",
"react-native-web": "^0.21.2"
},
"devDependencies": {
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 03f3e1a72..74bfbdf1c 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,23 +1,49 @@
import React from 'react';
-import { StatusBar } from 'react-native';
-import { NavigationContainer } from '@react-navigation/native';
-import { SafeAreaProvider } from 'react-native-safe-area-context';
-import RootNavigator from './navigation/RootNavigator';
+import { ScrollView, StatusBar, Text, View } from 'react-native';
+import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
+import { Auth0Provider } from 'react-native-auth0';
+import config from './auth0-configuration';
-/**
- * The absolute root component of the example application.
- *
- * It sets up the main navigation container and renders the RootNavigator,
- * which then decides which demo flow (Hooks or Class-based) to display.
- * The Auth0Provider is now scoped within the Hooks demo flow itself.
- */
+import WebAuthHooks from './features/WebAuthHooks';
+import CredentialsHooks from './features/CredentialsHooks';
+import DirectAuthHooks from './features/DirectAuthHooks';
+import PasswordlessHooks from './features/PasswordlessHooks';
+import MfaHooks from './features/MfaHooks';
+import MyAccountHooks from './features/MyAccountHooks';
+import PasskeysHooks from './features/PasskeysHooks';
+import AdvancedTokensHooks from './features/AdvancedTokensHooks';
+
+// Native example app. Every feature is demonstrated with the hooks approach
+// (wired up below). A matching *Class.tsx file sits next to each feature file
+// showing the same feature with the Auth0 class instance as unused reference.
+//
+// Biometric-protected credentials can be enabled by passing
+// localAuthenticationOptions to Auth0Provider, e.g.:
+//
function App(): React.JSX.Element {
return (
-
+
-
-
+
+
+
+ react-native-auth0 example
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/example/src/App.web.tsx b/example/src/App.web.tsx
index 777b650f8..ce06c0081 100644
--- a/example/src/App.web.tsx
+++ b/example/src/App.web.tsx
@@ -1,19 +1,8 @@
import React, { useState } from 'react';
-import {
- SafeAreaView,
- ScrollView,
- View,
- Text,
- StyleSheet,
- ActivityIndicator,
- TouchableOpacity,
- Image,
- Linking,
-} from 'react-native';
+import { Button, ScrollView, Text, TextInput, View } from 'react-native';
import Auth0, {
Auth0Provider,
useAuth0,
- User,
MfaError,
MfaErrorCodes,
MfaFactorType,
@@ -25,261 +14,205 @@ import type {
MfaAuthenticator,
MfaEnrollmentChallenge,
MfaChallengeResult,
+ DPoPHeadersParameters,
} from 'react-native-auth0';
import config from './auth0-configuration';
-import Button from './components/Button';
-import Header from './components/Header';
-import Result from './components/Result';
-import LabeledInput from './components/LabeledInput';
+import Section from './shared/Section';
+import ResultView from './shared/ResultView';
import { createWebPasskey } from './passkey/webPasskey';
-// My Account API is served from the `/me/` audience of the tenant. MRRT
-// (Multi-Resource Refresh Tokens) lets a single web session mint an access
-// token for this audience via `getApiCredentials` without a fresh redirect.
const MY_ACCOUNT_AUDIENCE = `https://${config.domain}/me/`;
const MY_ACCOUNT_SCOPE =
- 'read:me:authentication_methods delete:me:authentication_methods update:me:authentication_methods read:me:factors create:me:authentication_methods';
+ 'read:me:authentication_methods delete:me:authentication_methods ' +
+ 'update:me:authentication_methods read:me:factors create:me:authentication_methods';
type MfaStep =
- | 'idle'
- | 'list'
- | 'enroll-select'
- | 'enroll-details'
- | 'verify'
- | 'complete';
+ 'idle' | 'list' | 'enroll-select' | 'enroll-details' | 'verify' | 'complete';
type EnrollType = MfaFactorType;
-// ========================================================================
-// --- 1. HOOKS-BASED IMPLEMENTATION (Recommended) ---
-// ========================================================================
+function makeRun(
+ setResult: (r: unknown) => void,
+ setError: (e: Error | null) => void
+) {
+ return async (fn: () => Promise) => {
+ setResult(null);
+ setError(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+}
-const HooksAuthContent = (): React.JSX.Element => {
- const {
- authorize,
- clearSession,
- user,
- error,
- isLoading,
- getCredentials,
- getApiCredentials,
- createUser,
- resetPassword,
- loginWithPasswordRealm,
- customTokenExchange,
- mfa,
- myAccount,
- passkeySignupChallenge,
- passkeyLoginChallenge,
- getTokenByPasskey,
- } = useAuth0();
+// ============================================================
+// Hooks — Web Auth
+// ============================================================
- const [result, setResult] = useState(null);
- const [apiError, setApiError] = useState(null);
- const [email, setEmail] = useState('');
- const [password, setPassword] = useState('');
- const [passkeyEmail, setPasskeyEmail] = useState('');
- const [passkeyLoading, setPasskeyLoading] = useState(false);
+function WebAuthSection() {
+ const { authorize, clearSession, user } = useAuth0();
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const run = makeRun(setResult, setError);
- // Custom Token Exchange (RFC 8693) state
- const [subjectToken, setSubjectToken] = useState('');
- const [subjectTokenType, setSubjectTokenType] = useState(
- 'urn:acme:external-idp-token'
+ return (
+
+
+ {user ? (
+ <>
+ Logged in as {user.name ?? user.email ?? user.sub}
+ run(() => clearSession())}
+ />
+ >
+ ) : (
+
+ run(() =>
+ authorize({
+ audience: MY_ACCOUNT_AUDIENCE,
+ scope: `openid profile email offline_access ${MY_ACCOUNT_SCOPE}`,
+ })
+ )
+ }
+ />
+ )}
+
+ handleRedirectCallback(), getWebUser(), and checkWebSession() (Web
+ only) are called automatically by Auth0Provider on page load.
+
+
+
+
);
- const [actorToken, setActorToken] = useState('');
- const [actorTokenType, setActorTokenType] = useState(
- 'urn:ietf:params:oauth:token-type:id_token'
+}
+
+// ============================================================
+// Hooks — Credentials Manager
+// ============================================================
+
+function CredentialsSection() {
+ const { getCredentials, hasValidCredentials, clearCredentials } = useAuth0();
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const run = makeRun(setResult, setError);
+
+ return (
+
+
+ run(() => getCredentials())}
+ />
+ run(() => hasValidCredentials())}
+ />
+ run(() => clearCredentials())}
+ />
+
+ saveCredentials and clearApiCredentials are no-ops on web.
+ getSSOCredentials throws on web.
+
+
+
+
);
+}
- const fillActorTokenFromSession = async () => {
- setApiError(null);
- try {
- const credentials = await getCredentials();
- if (credentials?.idToken) {
- setActorToken(credentials.idToken);
- } else {
- setApiError(new Error('No ID token available in the current session.'));
- }
- } catch (e) {
- setApiError(e as Error);
- }
- };
+// ============================================================
+// Hooks — API Credentials / MRRT
+// ============================================================
+
+function ApiCredentialsSection() {
+ const { getApiCredentials } = useAuth0();
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [audience, setAudience] = useState(MY_ACCOUNT_AUDIENCE);
+ const [scope, setScope] = useState(MY_ACCOUNT_SCOPE);
+ const run = makeRun(setResult, setError);
+
+ return (
+
+
+
+
+ run(() => getApiCredentials(audience, scope))}
+ disabled={!audience}
+ />
+
+
+
+ );
+}
+
+// ============================================================
+// Hooks — MFA Flexible Factors
+// ============================================================
- // MFA wizard state
+function MfaSection() {
+ const { mfa } = useAuth0();
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
const [mfaToken, setMfaToken] = useState('');
- const [mfaStep, setMfaStep] = useState('idle');
- const [mfaLoading, setMfaLoading] = useState(false);
+ const [step, setStep] = useState('idle');
+ const [loading, setLoading] = useState(false);
const [authenticators, setAuthenticators] = useState([]);
- const [selectedAuthenticator, setSelectedAuthenticator] =
- useState(null);
const [enrollType, setEnrollType] = useState(null);
- const [enrollPhoneNumber, setEnrollPhoneNumber] = useState('');
+ const [enrollPhone, setEnrollPhone] = useState('');
const [enrollEmail, setEnrollEmail] = useState('');
const [enrollmentChallenge, setEnrollmentChallenge] =
useState(null);
const [challengeResult, setChallengeResult] =
useState(null);
const [verifyCode, setVerifyCode] = useState('');
- const [verifyBindingCode, setVerifyBindingCode] = useState('');
+ const [verifyBinding, setVerifyBinding] = useState('');
const [verifyScope, setVerifyScope] = useState('');
const [verifyAudience, setVerifyAudience] = useState('');
- // My Account state
- const [maResult, setMaResult] = useState(null);
- const [maError, setMaError] = useState(null);
- const [maLoading, setMaLoading] = useState(false);
- const [maToken, setMaToken] = useState(null);
- const [phoneNumber, setPhoneNumber] = useState('');
- const [emailAddress, setEmailAddress] = useState('');
- const [otpCode, setOtpCode] = useState('');
- const [methodId, setMethodId] = useState('');
- const [methodName, setMethodName] = useState('');
- const [enrollmentState, setEnrollmentState] = useState<{
- id: string;
- authSession: string;
- kind: 'phone' | 'email' | 'totp' | 'recovery';
- } | null>(null);
- const [passkeyChallenge, setPasskeyChallenge] = useState<{
- authenticationMethodId: string;
- authSession: string;
- authParamsPublicKey: Record;
- } | null>(null);
-
- const resetMfaWizard = () => {
- setMfaStep('idle');
+ const reset = () => {
+ setStep('idle');
setAuthenticators([]);
- setSelectedAuthenticator(null);
setEnrollType(null);
- setEnrollPhoneNumber('');
+ setEnrollPhone('');
setEnrollEmail('');
setEnrollmentChallenge(null);
setChallengeResult(null);
setVerifyCode('');
- setVerifyBindingCode('');
+ setVerifyBinding('');
setVerifyScope('');
setVerifyAudience('');
- setMfaLoading(false);
- };
-
- const runDemo = async (action: () => Promise) => {
- setResult(null);
- setApiError(null);
- try {
- const response = await action();
- setResult(response ?? { success: true });
- } catch (e) {
- if (e instanceof MfaError) {
- setApiError(e);
- return;
- }
- setApiError(e as Error);
- }
- };
-
- const handleMfaError = (e: unknown, fallbackMsg: string) => {
- if (e instanceof MfaError) {
- if (
- e.type === MfaErrorCodes.EXPIRED_MFA_TOKEN ||
- e.type === MfaErrorCodes.INVALID_MFA_TOKEN
- ) {
- setMfaToken('');
- resetMfaWizard();
- }
- setApiError(e);
- } else {
- setApiError(e as Error);
- }
- };
-
- const handlePasskeyError = (e: unknown) => {
- if (e instanceof PasskeyError) {
- setApiError(e);
- return;
- }
- setApiError(e as Error);
- };
-
- const onPasskeySignup = async () => {
- setResult(null);
- setApiError(null);
- setPasskeyLoading(true);
- try {
- const challenge = await passkeySignupChallenge({
- email: passkeyEmail || undefined,
- realm: 'Username-Password-Authentication',
- });
-
- // navigator.credentials isn't wrapped by the SDK — normalize a
- // cancelled/failed WebAuthn ceremony into a PasskeyError ourselves
- // so callers get the same PasskeyErrorCodes regardless of where the
- // failure occurred.
- let credential: PublicKeyCredential;
- try {
- credential = (await navigator.credentials.create({
- publicKey:
- challenge.authParamsPublicKey as PublicKeyCredentialCreationOptions,
- })) as PublicKeyCredential;
- } catch (e) {
- throw new PasskeyError(e as Error);
- }
-
- const credentials = await getTokenByPasskey({
- authSession: challenge.authSession,
- authResponse: credential,
- realm: 'Username-Password-Authentication',
- });
-
- setResult({
- success: true,
- accessToken: `${credentials.accessToken.substring(0, 30)}...`,
- });
- } catch (e) {
- handlePasskeyError(e);
- } finally {
- setPasskeyLoading(false);
- }
+ setLoading(false);
};
- const onPasskeyLogin = async () => {
- setResult(null);
- setApiError(null);
- setPasskeyLoading(true);
- try {
- const challenge = await passkeyLoginChallenge({
- realm: 'Username-Password-Authentication',
- });
-
- let credential: PublicKeyCredential;
- try {
- credential = (await navigator.credentials.get({
- publicKey:
- challenge.authParamsPublicKey as PublicKeyCredentialRequestOptions,
- })) as PublicKeyCredential;
- } catch (e) {
- throw new PasskeyError(e as Error);
- }
-
- const credentials = await getTokenByPasskey({
- authSession: challenge.authSession,
- authResponse: credential,
- realm: 'Username-Password-Authentication',
- });
-
- setResult({
- success: true,
- accessToken: `${credentials.accessToken.substring(0, 30)}...`,
- });
- } catch (e) {
- handlePasskeyError(e);
- } finally {
- setPasskeyLoading(false);
+ const handleMfaError = (e: unknown) => {
+ if (
+ e instanceof MfaError &&
+ (e.type === MfaErrorCodes.EXPIRED_MFA_TOKEN ||
+ e.type === MfaErrorCodes.INVALID_MFA_TOKEN)
+ ) {
+ setMfaToken('');
+ reset();
}
+ setError(e as Error);
};
- const onMfaStart = async () => {
- setMfaLoading(true);
- setApiError(null);
+ const onStart = async () => {
+ setLoading(true);
+ setError(null);
try {
const list = await mfa.getAuthenticators({
mfaToken,
@@ -292,55 +225,45 @@ const HooksAuthContent = (): React.JSX.Element => {
],
});
setAuthenticators(list);
- setMfaStep('list');
+ setStep('list');
} catch (e) {
- handleMfaError(e, 'Failed to list authenticators.');
+ handleMfaError(e);
} finally {
- setMfaLoading(false);
+ setLoading(false);
}
};
- const onMfaSelectAuthenticator = async (auth: MfaAuthenticator) => {
- setSelectedAuthenticator(auth);
- setMfaLoading(true);
+ const onChallenge = async (auth: MfaAuthenticator) => {
+ setLoading(true);
try {
const res = await mfa.challenge({ mfaToken, authenticatorId: auth.id });
setChallengeResult(res);
- setMfaStep('verify');
+ setStep('verify');
} catch (e) {
- handleMfaError(e, 'Challenge failed.');
- setMfaStep('list');
+ handleMfaError(e);
+ setStep('list');
} finally {
- setMfaLoading(false);
- }
- };
-
- const onMfaSelectEnrollType = (type: EnrollType) => {
- setEnrollType(type);
- if (type === MfaFactorType.OTP || type === MfaFactorType.PUSH) {
- onMfaEnroll(type);
- } else {
- setMfaStep('enroll-details');
+ setLoading(false);
}
};
- const onMfaEnroll = async (type?: EnrollType) => {
- const factor = type || enrollType;
+ const onEnroll = async (type?: EnrollType) => {
+ const factor = type ?? enrollType;
if (!factor) return;
- setMfaLoading(true);
+ setLoading(true);
try {
let challenge: MfaEnrollmentChallenge;
if (factor === MfaFactorType.SMS) {
challenge = await mfa.enroll({
mfaToken,
factorType: MfaFactorType.SMS,
- phoneNumber: enrollPhoneNumber,
+ phoneNumber: enrollPhone,
});
} else if (factor === MfaFactorType.VOICE) {
challenge = await mfa.enroll({
mfaToken,
factorType: MfaFactorType.VOICE,
- phoneNumber: enrollPhoneNumber,
+ phoneNumber: enrollPhone,
});
} else if (factor === MfaFactorType.EMAIL) {
challenge = await mfa.enroll({
@@ -352,36 +275,33 @@ const HooksAuthContent = (): React.JSX.Element => {
challenge = await mfa.enroll({ mfaToken, factorType: factor });
}
setEnrollmentChallenge(challenge);
- setMfaStep('verify');
+ setStep('verify');
} catch (e) {
- handleMfaError(e, 'Enrollment failed.');
+ handleMfaError(e);
} finally {
- setMfaLoading(false);
+ setLoading(false);
}
};
- const onMfaVerify = async () => {
- setMfaLoading(true);
+ const onVerify = async () => {
+ setLoading(true);
try {
- let credentials;
- // scope/audience are optional: supply them to mint an access token for a
- // specific API once MFA succeeds.
const extra = {
scope: verifyScope || undefined,
audience: verifyAudience || undefined,
};
const oobCode =
- challengeResult?.oobCode ||
+ challengeResult?.oobCode ??
(enrollmentChallenge?.type === 'oob' ||
enrollmentChallenge?.type === 'push'
? enrollmentChallenge.oobCode
: undefined);
-
+ let credentials;
if (oobCode) {
credentials = await mfa.verify({
mfaToken,
oobCode,
- bindingCode: verifyBindingCode || undefined,
+ bindingCode: verifyBinding || undefined,
...extra,
});
} else if (enrollmentChallenge?.type === 'recovery-code') {
@@ -397,1693 +317,914 @@ const HooksAuthContent = (): React.JSX.Element => {
success: true,
accessToken: credentials.accessToken.substring(0, 20) + '...',
});
- setMfaStep('complete');
+ setStep('complete');
} catch (e) {
- handleMfaError(e, 'Verification failed.');
+ handleMfaError(e);
} finally {
- setMfaLoading(false);
+ setLoading(false);
}
};
- // --- My Account helpers ---
+ return (
+
+
+ {step === 'idle' && (
+ <>
+
+ Obtain an mfa_token from a password-login attempt with MFA
+ enabled, then paste it here.
+
+
+
+ >
+ )}
- // Mint the `/me/` access token once via MRRT and cache it in state. Every
- // My Account call below reuses this token, so this button must be pressed
- // before any other My Account action.
- const onGetMyAccountToken = async () => {
- setMaResult(null);
- setMaError(null);
- setMaLoading(true);
- try {
- const credentials = await getApiCredentials(
- MY_ACCOUNT_AUDIENCE,
- MY_ACCOUNT_SCOPE
- );
- setMaToken(credentials.accessToken);
- setMaResult({
- step: 'getApiCredentials',
- audience: MY_ACCOUNT_AUDIENCE,
- accessToken: credentials.accessToken,
- expiresAt: credentials.expiresAt,
- scope: credentials.scope,
- });
- } catch (e) {
- setMaError(e as Error);
- } finally {
- setMaLoading(false);
- }
- };
+ {step === 'list' && (
+ <>
+ Select authenticator:
+ {authenticators.length === 0 && (
+ No authenticators enrolled.
+ )}
+ {authenticators.map((auth) => (
+ onChallenge(auth)}
+ disabled={loading}
+ />
+ ))}
+ setStep('enroll-select')}
+ />
+
+ >
+ )}
+
+ {step === 'enroll-select' && (
+ <>
+ Choose factor type:
+ {
+ setEnrollType(MfaFactorType.OTP);
+ onEnroll(MfaFactorType.OTP);
+ }}
+ disabled={loading}
+ />
+ {
+ setEnrollType(MfaFactorType.SMS);
+ setStep('enroll-details');
+ }}
+ disabled={loading}
+ />
+ {
+ setEnrollType(MfaFactorType.VOICE);
+ setStep('enroll-details');
+ }}
+ disabled={loading}
+ />
+ {
+ setEnrollType(MfaFactorType.EMAIL);
+ setStep('enroll-details');
+ }}
+ disabled={loading}
+ />
+ {
+ setEnrollType(MfaFactorType.PUSH);
+ onEnroll(MfaFactorType.PUSH);
+ }}
+ disabled={loading}
+ />
+ setStep('list')} />
+ >
+ )}
+
+ {step === 'enroll-details' && (
+ <>
+ {(enrollType === MfaFactorType.SMS ||
+ enrollType === MfaFactorType.VOICE) && (
+ <>
+
+ onEnroll()}
+ disabled={!enrollPhone || loading}
+ />
+ >
+ )}
+ {enrollType === MfaFactorType.EMAIL && (
+ <>
+
+ onEnroll()}
+ disabled={!enrollEmail || loading}
+ />
+ >
+ )}
+ setStep('enroll-select')} />
+ >
+ )}
+
+ {step === 'verify' && (
+ <>
+ mfa.verify()
+ {enrollmentChallenge?.type === 'recovery-code' ? (
+ <>
+
+ Recovery Code: {enrollmentChallenge.recoveryCode}
+
+
+
+ >
+ ) : challengeResult?.challengeType === 'oob' ||
+ enrollmentChallenge?.type === 'oob' ? (
+ <>
+ Code sent. Enter the binding code:
+
+
+ >
+ ) : enrollmentChallenge?.type === 'push' ? (
+ <>
+ Approve the push on your device, then:
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+ setStep('list')} />
+ >
+ )}
+
+ {step === 'complete' && (
+ <>
+ Authentication successful!
+
+ >
+ )}
+
+
+
+ );
+}
+
+// ============================================================
+// Hooks — My Account API
+// ============================================================
+
+function MyAccountSection() {
+ const { getApiCredentials, myAccount } = useAuth0();
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [maToken, setMaToken] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [emailAddress, setEmailAddress] = useState('');
+ const [otpCode, setOtpCode] = useState('');
+ const [methodId, setMethodId] = useState('');
+ const [methodName, setMethodName] = useState('');
+ const [enrollmentState, setEnrollmentState] = useState<{
+ id: string;
+ authSession: string;
+ kind: 'phone' | 'email' | 'totp' | 'recovery';
+ } | null>(null);
+ const [passkeyChallenge, setPasskeyChallenge] = useState<{
+ authenticationMethodId: string;
+ authSession: string;
+ authParamsPublicKey: Record;
+ } | null>(null);
- const runMyAccount = async (
- action: (accessToken: string) => Promise
- ) => {
+ const runMA = async (action: (token: string) => Promise) => {
if (!maToken) {
- setMaResult(null);
- setMaError(new Error('Get the My Account token (MRRT) first.'));
+ setError(new Error('Fetch the My Account token first.'));
return;
}
- setMaResult(null);
- setMaError(null);
- setMaLoading(true);
+ setResult(null);
+ setError(null);
+ setLoading(true);
try {
- const response = await action(maToken);
- setMaResult(response ?? { success: true });
+ const res = await action(maToken);
+ setResult(res ?? { success: true });
} catch (e) {
- // MyAccountError / PasskeyError carry richer fields than a plain message.
if (e instanceof MyAccountError) {
- setMaError(
+ setError(
new Error(
- `[${e.statusCode ?? ''}] ${e.title ?? 'My Account Error'}: ${
- e.detail ?? e.message
- }`
+ `[${e.statusCode ?? ''}] ${e.title ?? 'My Account Error'}: ${e.detail ?? e.message}`
)
);
- } else if (e instanceof PasskeyError) {
- setMaError(new Error(`[${e.type}] ${e.message}`));
} else {
- setMaError(e as Error);
+ setError(e as Error);
}
} finally {
- setMaLoading(false);
+ setLoading(false);
}
};
- const onEnrollPhone = () =>
- runMyAccount(async (accessToken) => {
- const challenge = await myAccount.enrollPhone({
- accessToken,
- phoneNumber: phoneNumber.trim(),
- preferredAuthenticationMethod: PreferredAuthenticationMethods.SMS,
- });
- setEnrollmentState({ ...challenge, kind: 'phone' });
- return { step: 'enrollPhone', ...challenge };
- });
+ return (
+
+
+
+ Uses MRRT to mint a /me/ access token via getApiCredentials. Fetch it
+ before calling any method below.
+
+
+ {
+ setResult(null);
+ setError(null);
+ setLoading(true);
+ try {
+ const creds = await getApiCredentials(
+ MY_ACCOUNT_AUDIENCE,
+ MY_ACCOUNT_SCOPE
+ );
+ setMaToken(creds.accessToken);
+ setResult({ step: 'getApiCredentials', ...creds });
+ } catch (e) {
+ setError(e as Error);
+ } finally {
+ setLoading(false);
+ }
+ }}
+ />
+ {maToken ? Token ready. : null}
+
+ Query
+
+ runMA(async (t) => {
+ const factors = await myAccount.getFactors({ accessToken: t });
+ return { step: 'getFactors', factors };
+ })
+ }
+ disabled={!maToken || loading}
+ />
+
+ runMA(async (t) => {
+ const methods = await myAccount.getAuthenticationMethods({
+ accessToken: t,
+ });
+ return {
+ step: 'getAuthenticationMethods',
+ count: methods.length,
+ methods,
+ };
+ })
+ }
+ disabled={!maToken || loading}
+ />
+
+
+ Passkey Enrollment (Web only — uses WebAuthn)
+
+
+ runMA(async (t) => {
+ const challenge = await myAccount.passkeyEnrollmentChallenge({
+ accessToken: t,
+ });
+ setPasskeyChallenge(challenge);
+ return {
+ step: 'passkeyEnrollmentChallenge',
+ authenticationMethodId: challenge.authenticationMethodId,
+ authSession: challenge.authSession,
+ };
+ })
+ }
+ disabled={!maToken || loading}
+ />
+
+ runMA(async (t) => {
+ if (!passkeyChallenge) {
+ throw new Error('Run the challenge first.');
+ }
+ const authResponse = await createWebPasskey(
+ passkeyChallenge.authParamsPublicKey
+ );
+ const method = await myAccount.enrollPasskey({
+ accessToken: t,
+ authenticationMethodId: passkeyChallenge.authenticationMethodId,
+ authSession: passkeyChallenge.authSession,
+ authResponse,
+ authParamsPublicKey: passkeyChallenge.authParamsPublicKey,
+ });
+ setPasskeyChallenge(null);
+ return { step: 'enrollPasskey', ...method };
+ })
+ }
+ disabled={!maToken || !passkeyChallenge || loading}
+ />
+
+
+ Phone Enrollment
+
+
+
+ runMA(async (t) => {
+ const challenge = await myAccount.enrollPhone({
+ accessToken: t,
+ phoneNumber: phoneNumber.trim(),
+ preferredAuthenticationMethod:
+ PreferredAuthenticationMethods.SMS,
+ });
+ setEnrollmentState({ ...challenge, kind: 'phone' });
+ return { step: 'enrollPhone', ...challenge };
+ })
+ }
+ disabled={!maToken || loading}
+ />
+
+
+ Email Enrollment
+
+
+
+ runMA(async (t) => {
+ const challenge = await myAccount.enrollEmail({
+ accessToken: t,
+ emailAddress: emailAddress.trim(),
+ });
+ setEnrollmentState({ ...challenge, kind: 'email' });
+ return { step: 'enrollEmail', ...challenge };
+ })
+ }
+ disabled={!maToken || loading}
+ />
+
+
+ TOTP / Recovery Code
+
+
+ runMA(async (t) => {
+ const challenge = await myAccount.enrollTOTP({ accessToken: t });
+ setEnrollmentState({
+ id: challenge.id,
+ authSession: challenge.authSession,
+ kind: 'totp',
+ });
+ return {
+ step: 'enrollTOTP',
+ id: challenge.id,
+ barcodeUri: challenge.barcodeUri,
+ manualInputCode: challenge.manualInputCode,
+ };
+ })
+ }
+ disabled={!maToken || loading}
+ />
+
+ runMA(async (t) => {
+ const challenge = await myAccount.enrollRecoveryCode({
+ accessToken: t,
+ });
+ setEnrollmentState({
+ id: challenge.id,
+ authSession: challenge.authSession,
+ kind: 'recovery',
+ });
+ return {
+ step: 'enrollRecoveryCode',
+ id: challenge.id,
+ recoveryCode: challenge.recoveryCode,
+ };
+ })
+ }
+ disabled={!maToken || loading}
+ />
+
+
+ Confirm Enrollment
+
+ {enrollmentState ? (
+
+ Pending: {enrollmentState.kind} (id {enrollmentState.id})
+
+ ) : null}
+
+
+ runMA(async (t) => {
+ if (!enrollmentState) {
+ throw new Error('Start an enrollment first.');
+ }
+ let method;
+ if (enrollmentState.kind === 'recovery') {
+ method = await myAccount.confirmRecoveryCodeEnrollment({
+ accessToken: t,
+ id: enrollmentState.id,
+ authSession: enrollmentState.authSession,
+ });
+ } else {
+ const confirmByKind = {
+ phone: myAccount.confirmPhoneEnrollment,
+ email: myAccount.confirmEmailEnrollment,
+ totp: myAccount.confirmTOTPEnrollment,
+ };
+ method = await confirmByKind[enrollmentState.kind].call(
+ myAccount,
+ {
+ accessToken: t,
+ id: enrollmentState.id,
+ authSession: enrollmentState.authSession,
+ otpCode: otpCode.trim(),
+ }
+ );
+ }
+ setEnrollmentState(null);
+ setOtpCode('');
+ return { step: 'confirmEnrollment', ...method };
+ })
+ }
+ disabled={!maToken || !enrollmentState || loading}
+ />
+
+ Update / Delete
+
+
+
+ runMA(async (t) => {
+ const method = await myAccount.updateAuthenticationMethodById({
+ accessToken: t,
+ id: methodId.trim(),
+ name: methodName.trim() || undefined,
+ });
+ return { step: 'updateAuthenticationMethodById', ...method };
+ })
+ }
+ disabled={!maToken || !methodId || loading}
+ />
+
+ runMA(async (t) => {
+ await myAccount.deleteAuthenticationMethodById({
+ accessToken: t,
+ id: methodId.trim(),
+ });
+ const deleted = methodId.trim();
+ setMethodId('');
+ return { step: 'deleteAuthenticationMethodById', deleted };
+ })
+ }
+ disabled={!maToken || !methodId || loading}
+ />
+
+
+
+ );
+}
- const onEnrollEmail = () =>
- runMyAccount(async (accessToken) => {
- const challenge = await myAccount.enrollEmail({
- accessToken,
- emailAddress: emailAddress.trim(),
- });
- setEnrollmentState({ ...challenge, kind: 'email' });
- return { step: 'enrollEmail', ...challenge };
- });
+// ============================================================
+// Hooks — Passkeys (browser WebAuthn)
+// ============================================================
- const onEnrollTOTP = () =>
- runMyAccount(async (accessToken) => {
- const challenge = await myAccount.enrollTOTP({ accessToken });
- setEnrollmentState({
- id: challenge.id,
- authSession: challenge.authSession,
- kind: 'totp',
- });
- return {
- step: 'enrollTOTP',
- id: challenge.id,
- barcodeUri: challenge.barcodeUri,
- manualInputCode: challenge.manualInputCode,
- };
- });
-
- const onEnrollRecoveryCode = () =>
- runMyAccount(async (accessToken) => {
- const challenge = await myAccount.enrollRecoveryCode({ accessToken });
- setEnrollmentState({
- id: challenge.id,
- authSession: challenge.authSession,
- kind: 'recovery',
- });
- return {
- step: 'enrollRecoveryCode',
- id: challenge.id,
- recoveryCode: challenge.recoveryCode,
- };
- });
-
- const onConfirmEnrollment = () =>
- runMyAccount(async (accessToken) => {
- if (!enrollmentState) {
- throw new Error('Start an enrollment first.');
- }
- let method;
- if (enrollmentState.kind === 'recovery') {
- method = await myAccount.confirmRecoveryCodeEnrollment({
- accessToken,
- id: enrollmentState.id,
- authSession: enrollmentState.authSession,
- });
- } else {
- const confirmByKind = {
- phone: myAccount.confirmPhoneEnrollment,
- email: myAccount.confirmEmailEnrollment,
- totp: myAccount.confirmTOTPEnrollment,
- };
- method = await confirmByKind[enrollmentState.kind].call(myAccount, {
- accessToken,
- id: enrollmentState.id,
- authSession: enrollmentState.authSession,
- otpCode: otpCode.trim(),
- });
- }
- setEnrollmentState(null);
- setOtpCode('');
- return { step: 'confirmEnrollment', ...method };
- });
-
- const onPasskeyChallenge = () =>
- runMyAccount(async (accessToken) => {
- const challenge = await myAccount.passkeyEnrollmentChallenge({
- accessToken,
- });
- setPasskeyChallenge(challenge);
- return {
- step: 'passkeyEnrollmentChallenge',
- authenticationMethodId: challenge.authenticationMethodId,
- authSession: challenge.authSession,
- };
- });
-
- const onPasskeyVerify = () =>
- runMyAccount(async (accessToken) => {
- if (!passkeyChallenge) {
- throw new Error('Run the passkey challenge first.');
- }
- const authResponse = await createWebPasskey(
- passkeyChallenge.authParamsPublicKey
- );
- const method = await myAccount.enrollPasskey({
- accessToken,
- authenticationMethodId: passkeyChallenge.authenticationMethodId,
- authSession: passkeyChallenge.authSession,
- authResponse,
- authParamsPublicKey: passkeyChallenge.authParamsPublicKey,
- });
- setPasskeyChallenge(null);
- return { step: 'enrollPasskey', ...method };
- });
-
- const onGetFactors = () =>
- runMyAccount(async (accessToken) => {
- const factors = await myAccount.getFactors({ accessToken });
- return { step: 'getFactors', factors };
- });
-
- const onGetAuthenticationMethods = () =>
- runMyAccount(async (accessToken) => {
- const methods = await myAccount.getAuthenticationMethods({ accessToken });
- return {
- step: 'getAuthenticationMethods',
- count: methods.length,
- methods,
- };
- });
-
- const onUpdateMethod = () =>
- runMyAccount(async (accessToken) => {
- const method = await myAccount.updateAuthenticationMethodById({
- accessToken,
- id: methodId.trim(),
- name: methodName.trim() || undefined,
- });
- return { step: 'updateAuthenticationMethodById', ...method };
- });
+function PasskeysSection() {
+ const { passkeySignupChallenge, passkeyLoginChallenge, getTokenByPasskey } =
+ useAuth0();
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [passkeyEmail, setPasskeyEmail] = useState('');
- const onDeleteMethod = () =>
- runMyAccount(async (accessToken) => {
- await myAccount.deleteAuthenticationMethodById({
- accessToken,
- id: methodId.trim(),
+ const onSignup = async () => {
+ setResult(null);
+ setError(null);
+ setLoading(true);
+ try {
+ const challenge = await passkeySignupChallenge({
+ email: passkeyEmail || undefined,
+ realm: 'Username-Password-Authentication',
});
- setMethodId('');
- return {
- step: 'deleteAuthenticationMethodById',
- deleted: methodId.trim(),
- };
- });
-
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- return (
-
-
- {error && }
-
- {user ? (
- <>
-
- Welcome, {user.name}!
-
- runDemo(getCredentials)}
- title="Get Credentials"
- />
-
-
-
-
-
- Custom Token Exchange (RFC 8693)
-
-
-
-
- runDemo(() =>
- customTokenExchange({ subjectToken, subjectTokenType })
- )
- }
- title="customTokenExchange()"
- disabled={!subjectToken || !subjectTokenType}
- />
-
- Delegation & Impersonation
-
-
-
-
- runDemo(() =>
- customTokenExchange({
- subjectToken,
- subjectTokenType,
- actorToken,
- actorTokenType,
- })
- )
- }
- title="customTokenExchange() with actor"
- disabled={
- !subjectToken ||
- !subjectTokenType ||
- !actorToken ||
- !actorTokenType
- }
- />
-
-
-
- My Account API
-
- Uses MRRT to mint a `/me/` access token via getApiCredentials.
-
-
-
- Access Token (MRRT)
-
- Fetch the `/me/` token before calling any My Account API below.
-
-
- {maToken && (
- Token ready — API calls enabled.
- )}
-
- Query
-
-
-
-
-
- Passkey Enrollment
-
-
-
-
-
- Phone Enrollment
-
-
-
- Email Enrollment
-
-
-
- TOTP / Recovery Code
-
-
-
-
-
- Confirm Enrollment
-
- After enrolling phone/email/TOTP enter the OTP; recovery-code
- enrollments confirm without an OTP.
-
-
-
- {enrollmentState && (
-
- Pending: {enrollmentState.kind} enrollment (id{' '}
- {enrollmentState.id})
-
- )}
-
- Update / Delete Method
-
-
-
-
-
-
-
- >
- ) : (
- <>
-
-
- authorize({
- // Request the My Account audience + scopes up front so the
- // refresh token carries them; getApiCredentials then resolves
- // from the refresh grant without an interactive step.
- audience: MY_ACCOUNT_AUDIENCE,
- scope: `openid profile email offline_access ${MY_ACCOUNT_SCOPE}`,
- })
- }
- title="Log In"
- />
-
-
-
-
-
- runDemo(async () => {
- try {
- return await loginWithPasswordRealm({
- username: email,
- password: password,
- realm: 'Username-Password-Authentication',
- });
- } catch (e: any) {
- if (e?.json?.mfa_token) {
- setMfaToken(e.json.mfa_token);
- }
- throw e;
- }
- })
- }
- title="Log In with Password"
- />
-
- If MFA is enabled, a failed login will return an mfa_token that
- auto-populates below.
-
-
- runDemo(() =>
- createUser({
- email,
- password,
- connection: 'Username-Password-Authentication',
- })
- )
- }
- title="Create User"
- />
-
- runDemo(() =>
- resetPassword({
- email,
- connection: 'Username-Password-Authentication',
- })
- )
- }
- title="Reset Password"
- />
-
-
- {mfaStep === 'idle' && (
- <>
-
- Get an mfa_token from a password login with MFA enabled.
-
-
-
- >
- )}
- {mfaStep === 'list' && (
- <>
-
- Step 1: Select Authenticator
-
- {authenticators.length > 0 ? (
- authenticators.map((auth) => (
- onMfaSelectAuthenticator(auth)}
- >
-
- {auth.type ?? auth.authenticatorType}
- {auth.oobChannel ? ` (${auth.oobChannel})` : ''}
-
-
- {auth.id} · authenticatorType: {auth.authenticatorType}
- {auth.active ? '' : ' · inactive'}
-
-
- ))
- ) : (
- No authenticators enrolled.
- )}
- setMfaStep('enroll-select')}
- title="Enroll New Authenticator"
- />
-
- >
- )}
- {mfaStep === 'enroll-select' && (
- <>
-
- Step 2: Choose Factor Type
-
- onMfaSelectEnrollType(MfaFactorType.OTP)}
- title="TOTP (Authenticator App)"
- disabled={mfaLoading}
- />
- onMfaSelectEnrollType(MfaFactorType.SMS)}
- title="SMS"
- disabled={mfaLoading}
- />
- onMfaSelectEnrollType(MfaFactorType.VOICE)}
- title="Voice"
- disabled={mfaLoading}
- />
-
- Voice is a distinct channel on web. On native it falls back to
- SMS on the same number.
-
- onMfaSelectEnrollType(MfaFactorType.EMAIL)}
- title="Email"
- disabled={mfaLoading}
- />
- onMfaSelectEnrollType(MfaFactorType.PUSH)}
- title="Push Notification"
- disabled={mfaLoading}
- />
- setMfaStep('list')} title="Back" />
- >
- )}
- {mfaStep === 'enroll-details' && (
- <>
- Step 2: Enter Details
- {(enrollType === MfaFactorType.SMS ||
- enrollType === MfaFactorType.VOICE) && (
- <>
-
- onMfaEnroll()}
- title={
- enrollType === MfaFactorType.VOICE
- ? 'Enroll Voice'
- : 'Enroll SMS'
- }
- disabled={!enrollPhoneNumber || mfaLoading}
- />
- >
- )}
- {enrollType === MfaFactorType.EMAIL && (
- <>
-
- onMfaEnroll()}
- title="Enroll Email"
- disabled={!enrollEmail || mfaLoading}
- />
- >
- )}
- setMfaStep('enroll-select')}
- title="Back"
- />
- >
- )}
- {mfaStep === 'verify' && (
- <>
- Step 3: Verify
- {enrollmentChallenge?.type === 'totp' && (
-
- {enrollmentChallenge.barcodeUri && (
- <>
-
-
-
-
- Linking.openURL(enrollmentChallenge.barcodeUri!)
- }
- title="Open in Authenticator App"
- />
- >
- )}
- Secret: {enrollmentChallenge.secret}
-
- )}
- {enrollmentChallenge?.type === 'push' && (
-
-
- Scan this QR with the Auth0 Guardian app to pair, then
- approve the push notification on your device.
-
- {enrollmentChallenge.barcodeUri ? (
-
-
-
- ) : null}
-
-
- )}
- {enrollmentChallenge?.type === 'recovery-code' ? (
-
-
- Save this recovery code — it is shown only once. Enter it
- below to complete verification.
-
-
- Recovery Code: {enrollmentChallenge.recoveryCode}
-
-
-
-
- ) : challengeResult?.challengeType === 'oob' ||
- enrollmentChallenge?.type === 'oob' ? (
- <>
-
- A code has been sent. Enter the binding code below.
-
-
-
- >
- ) : (
- <>
-
-
- >
- )}
-
- Optional: request a scope/audience to mint an API access token
- on successful verification.
-
-
-
- setMfaStep('list')} title="Back" />
- >
- )}
- {mfaStep === 'complete' && (
- <>
-
- Authentication successful!
-
- {result && (
-
- )}
-
- >
- )}
-
-
-
- Uses the browser's built-in WebAuthn API (navigator.credentials)
- via @auth0/auth0-spa-js.
-
-
-
-
-
- >
- )}
-
- );
-};
-
-const HooksApp = () => (
-
-
-
-);
-
-// ========================================================================
-// --- 2. CLASS-BASED IMPLEMENTATION ---
-// ========================================================================
-
-interface ClassAppState {
- auth0: Auth0;
- user: User | null;
- result: any;
- apiError: Error | null;
- isLoading: boolean;
- email: string;
- password: string;
- mfaToken: string;
- mfaStep: MfaStep;
- mfaLoading: boolean;
- authenticators: MfaAuthenticator[];
- enrollType: EnrollType | null;
- enrollPhoneNumber: string;
- enrollEmail: string;
- enrollmentChallenge: MfaEnrollmentChallenge | null;
- challengeResult: MfaChallengeResult | null;
- verifyCode: string;
- verifyBindingCode: string;
- verifyScope: string;
- verifyAudience: string;
-}
-
-class ClassApp extends React.Component<{}, ClassAppState> {
- state: ClassAppState = {
- auth0: new Auth0({
- domain: config.domain,
- clientId: config.clientId,
- useMrrt: true,
- cacheLocation: 'localstorage',
- useRefreshTokens: true,
- }),
- user: null,
- result: null,
- apiError: null,
- isLoading: true,
- email: '',
- password: '',
- mfaToken: '',
- mfaStep: 'idle',
- mfaLoading: false,
- authenticators: [],
- enrollType: null,
- enrollPhoneNumber: '',
- enrollEmail: '',
- enrollmentChallenge: null,
- challengeResult: null,
- verifyCode: '',
- verifyBindingCode: '',
- verifyScope: '',
- verifyAudience: '',
- };
-
- componentDidMount() {
- this.handleAuthentication();
- }
-
- handleAuthentication = async () => {
- const hasRedirectParams =
- typeof window !== 'undefined' &&
- (window.location.search.includes('code=') ||
- window.location.search.includes('error=')) &&
- window.location.search.includes('state=');
- if (hasRedirectParams) {
+ let credential: PublicKeyCredential;
try {
- await this.state.auth0.webAuth.handleRedirectCallback();
+ credential = (await navigator.credentials.create({
+ publicKey:
+ challenge.authParamsPublicKey as PublicKeyCredentialCreationOptions,
+ })) as PublicKeyCredential;
} catch (e) {
- this.setState({ apiError: e as Error, isLoading: false });
- } finally {
- if (typeof window !== 'undefined') {
- window.history.replaceState(
- {},
- document.title,
- window.location.pathname
- );
- }
+ throw new PasskeyError(e as Error);
}
- }
-
- try {
- const credentials =
- await this.state.auth0.credentialsManager.getCredentials();
- const user = await this.state.auth0.auth.userInfo({
- token: credentials.accessToken,
+ const creds = await getTokenByPasskey({
+ authSession: challenge.authSession,
+ authResponse: credential,
+ realm: 'Username-Password-Authentication',
});
- this.setState({ user, result: credentials, isLoading: false });
- } catch {
- this.setState({ user: null, isLoading: false });
- }
- };
-
- runDemo = async (action: () => Promise) => {
- this.setState({ result: null, apiError: null });
- try {
- const response = await action();
- this.setState({ result: response ?? { success: true } });
- } catch (e) {
- this.setState({ apiError: e as Error });
- }
- };
-
- onLogin = async () => {
- await this.state.auth0.webAuth.authorize({
- audience: MY_ACCOUNT_AUDIENCE,
- scope: `openid profile email offline_access ${MY_ACCOUNT_SCOPE}`,
- });
- };
-
- onLogout = async () => {
- try {
- await this.state.auth0.webAuth.clearSession();
- this.setState({ user: null, result: null, apiError: null });
- } catch (e) {
- this.setState({ apiError: e as Error });
- }
- };
-
- resetMfaWizard = () => {
- this.setState({
- mfaStep: 'idle',
- authenticators: [],
- enrollType: null,
- enrollPhoneNumber: '',
- enrollEmail: '',
- enrollmentChallenge: null,
- challengeResult: null,
- verifyCode: '',
- verifyBindingCode: '',
- verifyScope: '',
- verifyAudience: '',
- mfaLoading: false,
- });
- };
-
- onMfaStart = async () => {
- this.setState({ mfaLoading: true, apiError: null });
- try {
- const list = await this.state.auth0.mfa.getAuthenticators({
- mfaToken: this.state.mfaToken,
- factorsAllowed: [
- MfaFactorType.OTP,
- MfaFactorType.SMS,
- MfaFactorType.VOICE,
- MfaFactorType.EMAIL,
- MfaFactorType.PUSH,
- ],
+ setResult({
+ success: true,
+ accessToken: creds.accessToken.substring(0, 30) + '...',
});
- this.setState({ authenticators: list, mfaStep: 'list' });
} catch (e) {
- this.setState({ apiError: e as Error });
+ setError(e as Error);
} finally {
- this.setState({ mfaLoading: false });
+ setLoading(false);
}
};
- onMfaChallenge = async (auth: MfaAuthenticator) => {
- this.setState({ mfaLoading: true });
+ const onLogin = async () => {
+ setResult(null);
+ setError(null);
+ setLoading(true);
try {
- const res = await this.state.auth0.mfa.challenge({
- mfaToken: this.state.mfaToken,
- authenticatorId: auth.id,
+ const challenge = await passkeyLoginChallenge({
+ realm: 'Username-Password-Authentication',
});
- this.setState({ challengeResult: res, mfaStep: 'verify' });
- } catch (e) {
- this.setState({ apiError: e as Error, mfaStep: 'list' });
- } finally {
- this.setState({ mfaLoading: false });
- }
- };
-
- onMfaEnroll = async (type?: EnrollType) => {
- const factor = type || this.state.enrollType;
- if (!factor) return;
- this.setState({ mfaLoading: true });
- try {
- let challenge: MfaEnrollmentChallenge;
- const {
- mfaToken,
- enrollPhoneNumber: phone,
- enrollEmail: em,
- } = this.state;
- if (factor === MfaFactorType.SMS) {
- challenge = await this.state.auth0.mfa.enroll({
- mfaToken,
- factorType: MfaFactorType.SMS,
- phoneNumber: phone,
- });
- } else if (factor === MfaFactorType.VOICE) {
- challenge = await this.state.auth0.mfa.enroll({
- mfaToken,
- factorType: MfaFactorType.VOICE,
- phoneNumber: phone,
- });
- } else if (factor === MfaFactorType.EMAIL) {
- challenge = await this.state.auth0.mfa.enroll({
- mfaToken,
- factorType: MfaFactorType.EMAIL,
- email: em,
- });
- } else {
- challenge = await this.state.auth0.mfa.enroll({
- mfaToken,
- factorType: factor,
- });
+ let credential: PublicKeyCredential;
+ try {
+ credential = (await navigator.credentials.get({
+ publicKey:
+ challenge.authParamsPublicKey as PublicKeyCredentialRequestOptions,
+ })) as PublicKeyCredential;
+ } catch (e) {
+ throw new PasskeyError(e as Error);
}
- this.setState({ enrollmentChallenge: challenge, mfaStep: 'verify' });
+ const creds = await getTokenByPasskey({
+ authSession: challenge.authSession,
+ authResponse: credential,
+ realm: 'Username-Password-Authentication',
+ });
+ setResult({
+ success: true,
+ accessToken: creds.accessToken.substring(0, 30) + '...',
+ });
} catch (e) {
- this.setState({ apiError: e as Error });
+ setError(e as Error);
} finally {
- this.setState({ mfaLoading: false });
+ setLoading(false);
}
};
- onMfaVerify = async () => {
- this.setState({ mfaLoading: true });
+ return (
+
+
+
+ Uses navigator.credentials via @auth0/auth0-spa-js. Requires a
+ passkey-enabled connection.
+
+
+
+
+
+
+
+ );
+}
+
+// ============================================================
+// Hooks — Advanced Tokens (Custom Token Exchange, DPoP)
+// ============================================================
+
+function AdvancedTokensSection() {
+ const { customTokenExchange, getDPoPHeaders, getCredentials } = useAuth0();
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [subjectToken, setSubjectToken] = useState('');
+ const [subjectTokenType, setSubjectTokenType] = useState(
+ 'urn:acme:external-idp-token'
+ );
+ const [actorToken, setActorToken] = useState('');
+ const [actorTokenType, setActorTokenType] = useState(
+ 'urn:ietf:params:oauth:token-type:id_token'
+ );
+ const [dpopUrl, setDpopUrl] = useState('');
+ const [dpopMethod, setDpopMethod] = useState('GET');
+ const run = makeRun(setResult, setError);
+
+ const fillActorFromSession = async () => {
+ setError(null);
try {
- const {
- mfaToken,
- challengeResult,
- enrollmentChallenge,
- verifyCode,
- verifyBindingCode,
- verifyScope,
- verifyAudience,
- } = this.state;
- let credentials;
- // scope/audience are optional: supply them to mint an access token for a
- // specific API once MFA succeeds.
- const extra = {
- scope: verifyScope || undefined,
- audience: verifyAudience || undefined,
- };
- const oobCode =
- challengeResult?.oobCode ||
- (enrollmentChallenge?.type === 'oob' ||
- enrollmentChallenge?.type === 'push'
- ? enrollmentChallenge.oobCode
- : undefined);
- if (oobCode) {
- credentials = await this.state.auth0.mfa.verify({
- mfaToken,
- oobCode,
- bindingCode: verifyBindingCode || undefined,
- ...extra,
- });
- } else if (enrollmentChallenge?.type === 'recovery-code') {
- credentials = await this.state.auth0.mfa.verify({
- mfaToken,
- recoveryCode: verifyCode,
- ...extra,
- });
+ const creds = await getCredentials();
+ if (creds?.idToken) {
+ setActorToken(creds.idToken);
} else {
- credentials = await this.state.auth0.mfa.verify({
- mfaToken,
- otp: verifyCode,
- ...extra,
- });
+ setError(new Error('No ID token in current session.'));
}
- this.setState({
- result: {
- success: true,
- accessToken: credentials.accessToken.substring(0, 20) + '...',
- },
- mfaStep: 'complete',
- });
} catch (e) {
- this.setState({ apiError: e as Error });
- } finally {
- this.setState({ mfaLoading: false });
+ setError(e as Error);
}
};
- onGetMyAccountFactors = async () => {
- const credentials =
- await this.state.auth0.credentialsManager.getApiCredentials(
- MY_ACCOUNT_AUDIENCE,
- MY_ACCOUNT_SCOPE
- );
- return this.state.auth0.myAccount.getFactors({
- accessToken: credentials.accessToken,
- });
- };
-
- render() {
- const {
- user,
- result,
- apiError,
- isLoading,
- email,
- password,
- mfaToken,
- mfaStep,
- mfaLoading,
- authenticators,
- enrollType,
- enrollPhoneNumber,
- enrollEmail,
- enrollmentChallenge,
- challengeResult,
- verifyCode,
- verifyBindingCode,
- verifyScope,
- verifyAudience,
- } = this.state;
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
- {user ? (
-
- Welcome, {user.name}!
-
-
- this.runDemo(() =>
- this.state.auth0.credentialsManager.getCredentials()
- )
- }
- title="Get Credentials"
- />
- this.runDemo(this.onGetMyAccountFactors)}
- title="My Account: Get Factors (MRRT)"
- />
-
-
- ) : (
- <>
-
-
- this.setState({ email: val })}
- autoCapitalize="none"
- keyboardType="email-address"
- />
- this.setState({ password: val })}
- secureTextEntry
- />
-
- this.runDemo(async () => {
- try {
- return await this.state.auth0.auth.passwordRealm({
- username: email,
- password,
- realm: 'Username-Password-Authentication',
- });
- } catch (e: any) {
- if (e?.json?.mfa_token) {
- this.setState({ mfaToken: e.json.mfa_token });
- }
- throw e;
- }
- })
- }
- title="Log In with Password"
- />
-
- If MFA is enabled, a failed login will return an mfa_token that
- auto-populates below.
-
-
- this.runDemo(() =>
- this.state.auth0.auth.createUser({
- email,
- password,
- connection: 'Username-Password-Authentication',
- })
- )
- }
- title="Create User"
- />
-
- this.runDemo(() =>
- this.state.auth0.auth.resetPassword({
- email,
- connection: 'Username-Password-Authentication',
- })
- )
- }
- title="Reset Password"
- />
-
-
- {mfaStep === 'idle' && (
- <>
-
- Get an mfa_token from a password login with MFA enabled.
-
-
- this.setState({ mfaToken: val })
- }
- placeholder="Paste mfa_token here"
- />
-
- >
- )}
- {mfaStep === 'list' && (
- <>
-
- Step 1: Select Authenticator
-
- {authenticators.length > 0 ? (
- authenticators.map((auth) => (
- this.onMfaChallenge(auth)}
- >
-
- {auth.type ?? auth.authenticatorType}
- {auth.oobChannel ? ` (${auth.oobChannel})` : ''}
-
-
- {auth.id} · authenticatorType:{' '}
- {auth.authenticatorType}
- {auth.active ? '' : ' · inactive'}
-
-
- ))
- ) : (
- No authenticators enrolled.
- )}
- this.setState({ mfaStep: 'enroll-select' })}
- title="Enroll New Authenticator"
- />
-
- >
- )}
- {mfaStep === 'enroll-select' && (
- <>
-
- Step 2: Choose Factor Type
-
- {
- this.setState({ enrollType: MfaFactorType.OTP });
- this.onMfaEnroll(MfaFactorType.OTP);
- }}
- title="TOTP (Authenticator App)"
- disabled={mfaLoading}
- />
-
- this.setState({
- enrollType: MfaFactorType.SMS,
- mfaStep: 'enroll-details',
- })
- }
- title="SMS"
- disabled={mfaLoading}
- />
-
- this.setState({
- enrollType: MfaFactorType.VOICE,
- mfaStep: 'enroll-details',
- })
- }
- title="Voice"
- disabled={mfaLoading}
- />
-
- Voice is a distinct channel on web. On native it falls back
- to SMS on the same number.
-
-
- this.setState({
- enrollType: MfaFactorType.EMAIL,
- mfaStep: 'enroll-details',
- })
- }
- title="Email"
- disabled={mfaLoading}
- />
- {
- this.setState({ enrollType: MfaFactorType.PUSH });
- this.onMfaEnroll(MfaFactorType.PUSH);
- }}
- title="Push Notification"
- disabled={mfaLoading}
- />
- this.setState({ mfaStep: 'list' })}
- title="Back"
- />
- >
- )}
- {mfaStep === 'enroll-details' && (
- <>
- Step 2: Enter Details
- {(enrollType === MfaFactorType.SMS ||
- enrollType === MfaFactorType.VOICE) && (
- <>
-
- this.setState({ enrollPhoneNumber: val })
- }
- placeholder="+12025550135"
- />
- this.onMfaEnroll()}
- title={
- enrollType === MfaFactorType.VOICE
- ? 'Enroll Voice'
- : 'Enroll SMS'
- }
- disabled={!enrollPhoneNumber || mfaLoading}
- />
- >
- )}
- {enrollType === MfaFactorType.EMAIL && (
- <>
-
- this.setState({ enrollEmail: val })
- }
- placeholder="user@example.com"
- />
- this.onMfaEnroll()}
- title="Enroll Email"
- disabled={!enrollEmail || mfaLoading}
- />
- >
- )}
- this.setState({ mfaStep: 'enroll-select' })}
- title="Back"
- />
- >
- )}
- {mfaStep === 'verify' && (
- <>
- Step 3: Verify
- {enrollmentChallenge?.type === 'totp' && (
-
- {enrollmentChallenge.barcodeUri && (
- <>
-
-
-
-
- Linking.openURL(enrollmentChallenge.barcodeUri!)
- }
- title="Open in Authenticator App"
- />
- >
- )}
- Secret: {enrollmentChallenge.secret}
-
- )}
- {enrollmentChallenge?.type === 'push' && (
-
-
- Scan this QR with the Auth0 Guardian app to pair, then
- approve the push notification on your device.
-
- {enrollmentChallenge.barcodeUri ? (
-
-
-
- ) : null}
-
-
- )}
- {enrollmentChallenge?.type === 'recovery-code' ? (
-
-
- Save this recovery code — it is shown only once. Enter
- it below to complete verification.
-
-
- Recovery Code: {enrollmentChallenge.recoveryCode}
-
-
- this.setState({ verifyCode: val })
- }
- placeholder="Re-enter the recovery code"
- />
-
-
- ) : challengeResult?.challengeType === 'oob' ||
- enrollmentChallenge?.type === 'oob' ? (
- <>
-
- A code has been sent. Enter the binding code below.
-
-
- this.setState({ verifyBindingCode: val })
- }
- placeholder="Code from SMS/email"
- />
-
- >
- ) : (
- <>
-
- this.setState({ verifyCode: val })
- }
- placeholder="6-digit code"
- />
-
- >
- )}
-
- Optional: request a scope/audience to mint an API access
- token on successful verification.
-
-
- this.setState({ verifyScope: val })
- }
- placeholder="openid profile email"
- />
-
- this.setState({ verifyAudience: val })
- }
- placeholder={`https://${config.domain}/api/v2/`}
- />
- this.setState({ mfaStep: 'list' })}
- title="Back"
- />
- >
- )}
- {mfaStep === 'complete' && (
- <>
-
- Authentication successful!
-
- {result && (
-
- )}
-
- >
- )}
-
- >
- )}
-
- );
- }
-}
-
-// ========================================================================
-// --- 3. MAIN APP COMPONENT WITH TOGGLE ---
-// ========================================================================
-
-const App = (): React.JSX.Element => {
- const [showHooksDemo, setShowHooksDemo] = useState(true);
-
return (
-
-
- {showHooksDemo ? : }
-
-
- setShowHooksDemo(!showHooksDemo)}
- title={`Switch to ${showHooksDemo ? 'Class-Based' : 'Hooks'} Demo`}
- style={styles.toggleButton}
- />
-
-
-
+
+
+
+ customTokenExchange() (RFC 8693)
+
+
+
+
+ run(() => customTokenExchange({ subjectToken, subjectTokenType }))
+ }
+ disabled={!subjectToken || !subjectTokenType}
+ />
+
+
+ Delegation / Impersonation (with actor token):
+
+
+
+
+
+ run(() =>
+ customTokenExchange({
+ subjectToken,
+ subjectTokenType,
+ actorToken,
+ actorTokenType,
+ })
+ )
+ }
+ disabled={
+ !subjectToken || !subjectTokenType || !actorToken || !actorTokenType
+ }
+ />
+
+
+ getDPoPHeaders()
+
+
+ Requires an active session (useDPoP: true on the provider). Provide
+ the access token and token type from getCredentials, then call
+ getDPoPHeaders to generate the DPoP proof for a specific request.
+
+
+
+
+ run(async () => {
+ const creds = await getCredentials();
+ const params: DPoPHeadersParameters = {
+ url: dpopUrl,
+ method: dpopMethod,
+ accessToken: creds.accessToken,
+ tokenType: creds.tokenType ?? 'DPoP',
+ };
+ return getDPoPHeaders(params);
+ })
+ }
+ disabled={!dpopUrl || !dpopMethod}
+ />
+
+
+
);
-};
+}
-const Section = ({
- title,
- children,
-}: {
- title: string;
- children: React.ReactNode;
-}) => (
-
- {title}
- {children}
-
-);
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#F5F5F5' },
- content: {
- flex: 1,
- padding: 16,
- alignItems: 'center',
- justifyContent: 'center',
- },
- title: {
- fontSize: 24,
- fontWeight: 'bold',
- marginBottom: 24,
- textAlign: 'center',
- },
- section: {
- width: '100%',
- padding: 16,
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- marginBottom: 20,
- },
- sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 12 },
- subheading: {
- fontSize: 15,
- fontWeight: '600',
- marginTop: 16,
- marginBottom: 8,
- },
- buttonGroup: { gap: 10 },
- hint: { fontSize: 12, color: '#888', fontStyle: 'italic', marginBottom: 8 },
- toggleContainer: {
- padding: 16,
- alignItems: 'center',
- borderTopWidth: 1,
- borderTopColor: '#E0E0E0',
- backgroundColor: '#fafafa',
- },
- toggleButton: { backgroundColor: '#6c757d' },
+// ============================================================
+// Class approach (reference — never called at runtime)
+// Exported to satisfy noUnusedLocals. All methods are
+// self-consistent but not wired to any UI.
+// ============================================================
+
+const _auth0Class = new Auth0({
+ domain: config.domain,
+ clientId: config.clientId,
+ useMrrt: true,
+ cacheLocation: 'localstorage',
+ useRefreshTokens: true,
});
-const webStyles = StyleSheet.create({
- authItem: {
- borderWidth: 1,
- borderColor: '#CCC',
- borderRadius: 6,
- padding: 12,
- backgroundColor: '#F9F9F9',
- marginBottom: 8,
- },
- authItemTitle: { fontSize: 14, fontWeight: '600' },
- authItemSub: { fontSize: 11, color: '#666', marginTop: 2 },
- infoBox: {
- backgroundColor: '#F0F4FF',
- borderRadius: 6,
- padding: 10,
- marginBottom: 8,
+export const classReference = {
+ // Web Auth
+ authorize: () =>
+ _auth0Class.webAuth.authorize({
+ audience: MY_ACCOUNT_AUDIENCE,
+ scope: `openid profile email offline_access ${MY_ACCOUNT_SCOPE}`,
+ }),
+ clearSession: () => _auth0Class.webAuth.clearSession(),
+ // Web only
+ handleRedirectCallback: () => _auth0Class.webAuth.handleRedirectCallback(),
+ getWebUser: () => _auth0Class.webAuth.getWebUser(),
+ checkWebSession: () => _auth0Class.webAuth.checkWebSession(),
+ // Credentials Manager
+ getCredentials: () => _auth0Class.credentialsManager.getCredentials(),
+ hasValidCredentials: () =>
+ _auth0Class.credentialsManager.hasValidCredentials(),
+ clearCredentials: () => _auth0Class.credentialsManager.clearCredentials(),
+ // API Credentials / MRRT
+ getApiCredentials: (audience: string, scope: string) =>
+ _auth0Class.credentialsManager.getApiCredentials(audience, scope),
+ // MFA
+ mfaGetAuthenticators: (mfaToken: string) =>
+ _auth0Class.mfa.getAuthenticators({ mfaToken }),
+ mfaChallenge: (mfaToken: string, authenticatorId: string) =>
+ _auth0Class.mfa.challenge({ mfaToken, authenticatorId }),
+ mfaEnrollOTP: (mfaToken: string) =>
+ _auth0Class.mfa.enroll({ mfaToken, factorType: MfaFactorType.OTP }),
+ mfaVerify: (mfaToken: string, otp: string) =>
+ _auth0Class.mfa.verify({ mfaToken, otp }),
+ // My Account
+ getFactors: async () => {
+ const creds = await _auth0Class.credentialsManager.getApiCredentials(
+ MY_ACCOUNT_AUDIENCE,
+ MY_ACCOUNT_SCOPE
+ );
+ return _auth0Class.myAccount.getFactors({ accessToken: creds.accessToken });
},
- successText: {
- fontSize: 16,
- fontWeight: '600',
- color: '#2E7D32',
- textAlign: 'center',
- marginBottom: 12,
+ getAuthenticationMethods: async () => {
+ const creds = await _auth0Class.credentialsManager.getApiCredentials(
+ MY_ACCOUNT_AUDIENCE,
+ MY_ACCOUNT_SCOPE
+ );
+ return _auth0Class.myAccount.getAuthenticationMethods({
+ accessToken: creds.accessToken,
+ });
},
- qrContainer: { alignItems: 'center', marginVertical: 12 },
- qrImage: { width: 200, height: 200 },
-});
+ // Advanced Tokens
+ customTokenExchange: (subjectToken: string, subjectTokenType: string) =>
+ _auth0Class.customTokenExchange({ subjectToken, subjectTokenType }),
+ getDPoPHeaders: (params: DPoPHeadersParameters) =>
+ _auth0Class.getDPoPHeaders(params),
+};
-export default App;
+// ============================================================
+// App
+// ============================================================
+
+function Content() {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default function App() {
+ return (
+
+
+
+ );
+}
diff --git a/example/src/api/auth0.ts b/example/src/api/auth0.ts
deleted file mode 100644
index d77a0eeaf..000000000
--- a/example/src/api/auth0.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import Auth0 from 'react-native-auth0';
-import config from '../auth0-configuration';
-
-const AUTH0_DOMAIN = config.domain;
-const AUTH0_CLIENT_ID = config.clientId;
-
-if (!AUTH0_DOMAIN || !AUTH0_CLIENT_ID) {
- throw new Error(
- 'Missing Auth0 credentials. Please add AUTH0_DOMAIN and AUTH0_CLIENT_ID to your environment variables.'
- );
-}
-
-const auth0 = new Auth0({
- domain: AUTH0_DOMAIN,
- clientId: AUTH0_CLIENT_ID,
-});
-
-export default auth0;
diff --git a/example/src/components/Button.tsx b/example/src/components/Button.tsx
deleted file mode 100644
index 5b7dfa50a..000000000
--- a/example/src/components/Button.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from 'react';
-import {
- TouchableOpacity,
- Text,
- StyleSheet,
- ViewStyle,
- TextStyle,
- ActivityIndicator,
-} from 'react-native';
-
-type Props = {
- onPress: () => void;
- title: string;
- disabled?: boolean;
- loading?: boolean;
- style?: ViewStyle;
- textStyle?: TextStyle;
-};
-
-const Button = ({
- onPress,
- title,
- disabled,
- loading,
- style,
- textStyle,
-}: Props) => {
- return (
-
- {loading ? (
-
- ) : (
- {title}
- )}
-
- );
-};
-
-const styles = StyleSheet.create({
- button: {
- backgroundColor: '#E53935', // A distinct color for Auth0
- paddingVertical: 12,
- paddingHorizontal: 24,
- borderRadius: 8,
- alignItems: 'center',
- justifyContent: 'center',
- minWidth: 200,
- },
- text: {
- color: '#FFFFFF',
- fontSize: 16,
- fontWeight: 'bold',
- },
- disabled: {
- backgroundColor: '#BDBDBD',
- },
-});
-
-export default Button;
diff --git a/example/src/components/Header.tsx b/example/src/components/Header.tsx
deleted file mode 100644
index 1121f80e8..000000000
--- a/example/src/components/Header.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { View, Text, StyleSheet } from 'react-native';
-
-type Props = {
- title: string;
-};
-
-const Header = ({ title }: Props) => {
- return (
-
- {title}
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- padding: 20,
- backgroundColor: '#F5F5F5',
- borderBottomWidth: 1,
- borderBottomColor: '#E0E0E0',
- alignItems: 'center',
- },
- title: {
- fontSize: 22,
- fontWeight: 'bold',
- color: '#212121',
- },
-});
-
-export default Header;
diff --git a/example/src/components/LabeledInput.tsx b/example/src/components/LabeledInput.tsx
deleted file mode 100644
index f6ed8977d..000000000
--- a/example/src/components/LabeledInput.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import {
- View,
- Text,
- TextInput,
- StyleSheet,
- TextInputProps,
-} from 'react-native';
-
-type Props = TextInputProps & {
- label: string;
-};
-
-const LabeledInput = ({ label, ...props }: Props) => {
- return (
-
- {label}
-
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- width: '100%',
- marginBottom: 16,
- },
- label: {
- marginBottom: 8,
- fontSize: 14,
- color: '#424242',
- fontWeight: '500',
- },
- input: {
- backgroundColor: '#FAFAFA',
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- padding: 12,
- fontSize: 16,
- color: '#212121',
- },
-});
-
-export default LabeledInput;
diff --git a/example/src/components/Result.tsx b/example/src/components/Result.tsx
deleted file mode 100644
index bd44ca029..000000000
--- a/example/src/components/Result.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import React from 'react';
-import { View, Text, StyleSheet, Platform } from 'react-native';
-
-type Props = {
- title: string;
- result: object | null | void;
- error: Error | null;
-};
-
-const Result = ({ title, result, error }: Props) => {
- if (!result && !error) {
- return null;
- }
-
- const isError = !!error;
- const content = error ? error.message : JSON.stringify(result, null, 2);
-
- return (
-
- {title}
-
-
- {content}
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- marginVertical: 10,
- width: '100%',
- },
- title: {
- fontSize: 16,
- fontWeight: 'bold',
- marginBottom: 8,
- },
- resultBox: {
- backgroundColor: '#E8F5E9',
- borderColor: '#A5D6A7',
- borderWidth: 1,
- borderRadius: 8,
- padding: 12,
- },
- errorBox: {
- backgroundColor: '#FFEBEE',
- borderColor: '#EF9A9A',
- },
- resultText: {
- fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
- color: '#1B5E20',
- },
- errorText: {
- fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
- color: '#B71C1C',
- },
-});
-
-export default Result;
diff --git a/example/src/components/UserInfo.tsx b/example/src/components/UserInfo.tsx
deleted file mode 100644
index 3191981ef..000000000
--- a/example/src/components/UserInfo.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-import { View, Text, StyleSheet, Image } from 'react-native';
-import type { User } from 'react-native-auth0';
-
-type Props = {
- user: User | null;
-};
-
-const UserInfo = ({ user }: Props) => {
- if (!user) {
- return null;
- }
-
- return (
-
- {user.picture && (
-
- )}
- {user.name}
- {Object.entries(user).map(([key, value]) => (
-
- {key}
- {String(value)}
-
- ))}
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- width: '100%',
- padding: 16,
- },
- avatar: {
- width: 100,
- height: 100,
- borderRadius: 50,
- alignSelf: 'center',
- marginBottom: 16,
- },
- title: {
- fontSize: 24,
- fontWeight: 'bold',
- textAlign: 'center',
- marginBottom: 20,
- color: '#212121',
- },
- row: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- paddingVertical: 8,
- borderBottomWidth: 1,
- borderBottomColor: '#EEEEEE',
- },
- label: {
- fontSize: 16,
- color: '#757575',
- flex: 1,
- },
- value: {
- fontSize: 16,
- color: '#212121',
- fontWeight: '500',
- flex: 2,
- textAlign: 'right',
- },
-});
-
-export default UserInfo;
diff --git a/example/src/features/AdvancedTokensClass.tsx b/example/src/features/AdvancedTokensClass.tsx
new file mode 100644
index 000000000..0c1fdd9cc
--- /dev/null
+++ b/example/src/features/AdvancedTokensClass.tsx
@@ -0,0 +1,129 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import auth0 from '../shared/api';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+// Class-based reference for Advanced Token operations. Same operations as
+// AdvancedTokensHooks.tsx, expressed against the Auth0 class instance. Not
+// imported by the app — kept as a side-by-side reference.
+const AdvancedTokensClass = () => {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+
+ // Custom Token Exchange
+ const [subjectToken, setSubjectToken] = useState('');
+ const [subjectTokenType, setSubjectTokenType] = useState(
+ 'urn:acme:external-idp-token'
+ );
+ const [actorToken, setActorToken] = useState('');
+ const [actorTokenType, setActorTokenType] = useState(
+ 'urn:ietf:params:oauth:token-type:id_token'
+ );
+
+ // DPoP Headers
+ const [url, setUrl] = useState('');
+ const [method, setMethod] = useState('GET');
+ const [accessToken, setAccessToken] = useState('');
+ const [tokenType, setTokenType] = useState('DPoP');
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+
+
+
+ run(() =>
+ auth0.customTokenExchange({ subjectToken, subjectTokenType })
+ )
+ }
+ disabled={!subjectToken || !subjectTokenType}
+ />
+
+
+
+
+ run(() =>
+ auth0.customTokenExchange({
+ subjectToken,
+ subjectTokenType,
+ actorToken,
+ actorTokenType,
+ })
+ )
+ }
+ disabled={
+ !subjectToken || !subjectTokenType || !actorToken || !actorTokenType
+ }
+ />
+
+
+
+
+
+
+
+
+ run(() =>
+ auth0.getDPoPHeaders({ url, method, accessToken, tokenType })
+ )
+ }
+ disabled={!url || !method || !accessToken || !tokenType}
+ />
+
+
+
+
+ );
+};
+
+export default AdvancedTokensClass;
diff --git a/example/src/features/AdvancedTokensHooks.tsx b/example/src/features/AdvancedTokensHooks.tsx
new file mode 100644
index 000000000..2906d0925
--- /dev/null
+++ b/example/src/features/AdvancedTokensHooks.tsx
@@ -0,0 +1,124 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { useAuth0 } from 'react-native-auth0';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+const AdvancedTokensHooks = () => {
+ const { customTokenExchange, getDPoPHeaders } = useAuth0();
+
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+
+ // Custom Token Exchange
+ const [subjectToken, setSubjectToken] = useState('');
+ const [subjectTokenType, setSubjectTokenType] = useState(
+ 'urn:acme:external-idp-token'
+ );
+ const [actorToken, setActorToken] = useState('');
+ const [actorTokenType, setActorTokenType] = useState(
+ 'urn:ietf:params:oauth:token-type:id_token'
+ );
+
+ // DPoP Headers
+ const [url, setUrl] = useState('');
+ const [method, setMethod] = useState('GET');
+ const [accessToken, setAccessToken] = useState('');
+ const [tokenType, setTokenType] = useState('DPoP');
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+
+
+
+ run(() => customTokenExchange({ subjectToken, subjectTokenType }))
+ }
+ disabled={!subjectToken || !subjectTokenType}
+ />
+
+
+
+
+ run(() =>
+ customTokenExchange({
+ subjectToken,
+ subjectTokenType,
+ actorToken,
+ actorTokenType,
+ })
+ )
+ }
+ disabled={
+ !subjectToken || !subjectTokenType || !actorToken || !actorTokenType
+ }
+ />
+
+
+
+
+
+
+
+
+ run(() => getDPoPHeaders({ url, method, accessToken, tokenType }))
+ }
+ disabled={!url || !method || !accessToken || !tokenType}
+ />
+
+
+
+
+ );
+};
+
+export default AdvancedTokensHooks;
diff --git a/example/src/features/CredentialsClass.tsx b/example/src/features/CredentialsClass.tsx
new file mode 100644
index 000000000..338c0d215
--- /dev/null
+++ b/example/src/features/CredentialsClass.tsx
@@ -0,0 +1,96 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { Credentials } from 'react-native-auth0';
+import auth0 from '../shared/api';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+// Class-based reference for the Credentials Manager. Same operations as
+// CredentialsHooks.tsx, expressed against the Auth0 class instance. Not imported
+// by the app — kept as a side-by-side reference.
+const CredentialsClass = () => {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [audience, setAudience] = useState('');
+ const [scope, setScope] = useState('openid profile email');
+ const [last, setLast] = useState(null);
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+ run(async () => {
+ const creds = await auth0.credentialsManager.getCredentials();
+ setLast(creds ?? null);
+ return creds;
+ })
+ }
+ />
+
+ run(() => auth0.credentialsManager.hasValidCredentials())
+ }
+ />
+
+ run(() =>
+ auth0.credentialsManager.saveCredentials(last as Credentials)
+ )
+ }
+ disabled={!last}
+ />
+ run(() => auth0.credentialsManager.clearCredentials())}
+ />
+
+
+
+
+
+ run(() =>
+ auth0.credentialsManager.getApiCredentials(audience, scope)
+ )
+ }
+ disabled={!audience}
+ />
+
+ run(() => auth0.credentialsManager.clearApiCredentials(audience))
+ }
+ disabled={!audience}
+ />
+
+
+ run(() => auth0.credentialsManager.getSSOCredentials())}
+ />
+
+
+
+ );
+};
+
+export default CredentialsClass;
diff --git a/example/src/features/CredentialsHooks.tsx b/example/src/features/CredentialsHooks.tsx
new file mode 100644
index 000000000..ebdb813f8
--- /dev/null
+++ b/example/src/features/CredentialsHooks.tsx
@@ -0,0 +1,90 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { useAuth0, Credentials } from 'react-native-auth0';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+const CredentialsHooks = () => {
+ const {
+ getCredentials,
+ saveCredentials,
+ hasValidCredentials,
+ clearCredentials,
+ getApiCredentials,
+ clearApiCredentials,
+ getSSOCredentials,
+ } = useAuth0();
+
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [audience, setAudience] = useState('');
+ const [scope, setScope] = useState('openid profile email');
+ const [last, setLast] = useState(null);
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+ run(async () => {
+ const creds = await getCredentials();
+ setLast(creds ?? null);
+ return creds;
+ })
+ }
+ />
+ run(() => hasValidCredentials())}
+ />
+ run(() => saveCredentials(last as Credentials))}
+ disabled={!last}
+ />
+ run(() => clearCredentials())}
+ />
+
+
+
+
+ run(() => getApiCredentials(audience, scope))}
+ disabled={!audience}
+ />
+ run(() => clearApiCredentials(audience))}
+ disabled={!audience}
+ />
+
+
+ run(() => getSSOCredentials())}
+ />
+
+
+
+ );
+};
+
+export default CredentialsHooks;
diff --git a/example/src/features/DirectAuthClass.tsx b/example/src/features/DirectAuthClass.tsx
new file mode 100644
index 000000000..28083ffae
--- /dev/null
+++ b/example/src/features/DirectAuthClass.tsx
@@ -0,0 +1,155 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import auth0 from '../shared/api';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+// Class-based reference for the Direct Authentication API. Same operations as
+// DirectAuthHooks.tsx (plus userInfo and refreshToken), expressed against the
+// Auth0 class instance. Not imported by the app — kept as a side-by-side reference.
+const DirectAuthClass = () => {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [username, setUsername] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [connection, setConnection] = useState(
+ 'Username-Password-Authentication'
+ );
+ const [accessToken, setAccessToken] = useState('');
+ const [refreshToken, setRefreshToken] = useState('');
+ const [subjectToken, setSubjectToken] = useState('');
+ const [subjectTokenType, setSubjectTokenType] = useState(
+ 'http://auth0.com/oauth/token-type/google-access-token'
+ );
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ run(() =>
+ auth0.auth.passwordRealm({
+ username,
+ password,
+ realm: connection,
+ })
+ )
+ }
+ disabled={!username || !password || !connection}
+ />
+
+
+
+
+
+ run(() => auth0.auth.createUser({ email, password, connection }))
+ }
+ disabled={!email || !password || !connection}
+ />
+
+ run(() => auth0.auth.resetPassword({ email, connection }))
+ }
+ disabled={!email || !connection}
+ />
+
+
+
+
+ run(() => auth0.auth.userInfo({ token: accessToken }))}
+ disabled={!accessToken}
+ />
+
+
+
+
+ run(() => auth0.auth.refreshToken({ refreshToken }))}
+ disabled={!refreshToken}
+ />
+ run(() => auth0.auth.revoke({ refreshToken }))}
+ disabled={!refreshToken}
+ />
+
+
+
+
+
+
+ run(() =>
+ auth0.auth.exchangeNativeSocial({
+ subjectToken,
+ subjectTokenType,
+ })
+ )
+ }
+ disabled={!subjectToken || !subjectTokenType}
+ />
+
+
+
+
+ );
+};
+
+export default DirectAuthClass;
diff --git a/example/src/features/DirectAuthHooks.tsx b/example/src/features/DirectAuthHooks.tsx
new file mode 100644
index 000000000..802de704c
--- /dev/null
+++ b/example/src/features/DirectAuthHooks.tsx
@@ -0,0 +1,133 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { useAuth0 } from 'react-native-auth0';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+const DirectAuthHooks = () => {
+ const {
+ loginWithPasswordRealm,
+ createUser,
+ resetPassword,
+ revokeRefreshToken,
+ authorizeWithExchangeNativeSocial,
+ } = useAuth0();
+
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [username, setUsername] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [connection, setConnection] = useState(
+ 'Username-Password-Authentication'
+ );
+ const [refreshToken, setRefreshToken] = useState('');
+ const [subjectToken, setSubjectToken] = useState('');
+ const [subjectTokenType, setSubjectTokenType] = useState(
+ 'http://auth0.com/oauth/token-type/google-access-token'
+ );
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ run(() =>
+ loginWithPasswordRealm({ username, password, realm: connection })
+ )
+ }
+ disabled={!username || !password || !connection}
+ />
+
+
+
+
+ run(() => createUser({ email, password, connection }))}
+ disabled={!email || !password || !connection}
+ />
+ run(() => resetPassword({ email, connection }))}
+ disabled={!email || !connection}
+ />
+
+
+
+
+ run(() => revokeRefreshToken({ refreshToken }))}
+ disabled={!refreshToken}
+ />
+
+
+
+
+
+
+ run(() =>
+ authorizeWithExchangeNativeSocial({
+ subjectToken,
+ subjectTokenType,
+ })
+ )
+ }
+ disabled={!subjectToken || !subjectTokenType}
+ />
+
+
+
+
+ );
+};
+
+export default DirectAuthHooks;
diff --git a/example/src/features/MfaClass.tsx b/example/src/features/MfaClass.tsx
new file mode 100644
index 000000000..37da65873
--- /dev/null
+++ b/example/src/features/MfaClass.tsx
@@ -0,0 +1,191 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { MfaFactorType } from 'react-native-auth0';
+import auth0 from '../shared/api';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+// Class-based reference for MFA (Flexible Factors Grant). Same operations as
+// MfaHooks.tsx, expressed against the Auth0 class instance. Not imported
+// by the app — kept as a side-by-side reference.
+const MfaClass = () => {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [mfaToken, setMfaToken] = useState('');
+ const [otpCode, setOtpCode] = useState('');
+ const [oobCode, setOobCode] = useState('');
+ const [bindingCode, setBindingCode] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [enrollEmail, setEnrollEmail] = useState('');
+ const [authenticatorId, setAuthenticatorId] = useState('');
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+
+
+ run(() =>
+ auth0.mfa.getAuthenticators({
+ mfaToken,
+ factorsAllowed: [
+ MfaFactorType.OTP,
+ MfaFactorType.SMS,
+ MfaFactorType.EMAIL,
+ MfaFactorType.PUSH,
+ MfaFactorType.VOICE,
+ ],
+ })
+ )
+ }
+ disabled={!mfaToken}
+ />
+
+
+
+
+
+ run(() =>
+ auth0.mfa.enroll({ mfaToken, factorType: MfaFactorType.OTP })
+ )
+ }
+ disabled={!mfaToken}
+ />
+
+ run(() =>
+ auth0.mfa.enroll({
+ mfaToken,
+ factorType: MfaFactorType.SMS,
+ phoneNumber,
+ })
+ )
+ }
+ disabled={!mfaToken || !phoneNumber}
+ />
+
+ run(() =>
+ auth0.mfa.enroll({
+ mfaToken,
+ factorType: MfaFactorType.VOICE,
+ phoneNumber,
+ })
+ )
+ }
+ disabled={!mfaToken || !phoneNumber}
+ />
+
+ run(() =>
+ auth0.mfa.enroll({
+ mfaToken,
+ factorType: MfaFactorType.EMAIL,
+ email: enrollEmail,
+ })
+ )
+ }
+ disabled={!mfaToken || !enrollEmail}
+ />
+
+ run(() =>
+ auth0.mfa.enroll({ mfaToken, factorType: MfaFactorType.PUSH })
+ )
+ }
+ disabled={!mfaToken}
+ />
+
+
+
+
+
+ run(() => auth0.mfa.challenge({ mfaToken, authenticatorId }))
+ }
+ disabled={!mfaToken || !authenticatorId}
+ />
+
+
+
+
+
+
+
+ run(() => auth0.mfa.verify({ mfaToken, otp: otpCode }))
+ }
+ disabled={!mfaToken || !otpCode}
+ />
+
+ run(() =>
+ auth0.mfa.verify({
+ mfaToken,
+ oobCode,
+ bindingCode: bindingCode || undefined,
+ })
+ )
+ }
+ disabled={!mfaToken || !oobCode}
+ />
+
+
+
+
+ );
+};
+
+export default MfaClass;
diff --git a/example/src/features/MfaHooks.tsx b/example/src/features/MfaHooks.tsx
new file mode 100644
index 000000000..e998ba8f9
--- /dev/null
+++ b/example/src/features/MfaHooks.tsx
@@ -0,0 +1,183 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { useAuth0, MfaFactorType } from 'react-native-auth0';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+const MfaHooks = () => {
+ const { mfa } = useAuth0();
+
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [mfaToken, setMfaToken] = useState('');
+ const [otpCode, setOtpCode] = useState('');
+ const [oobCode, setOobCode] = useState('');
+ const [bindingCode, setBindingCode] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [enrollEmail, setEnrollEmail] = useState('');
+ const [authenticatorId, setAuthenticatorId] = useState('');
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+
+
+ run(() =>
+ mfa.getAuthenticators({
+ mfaToken,
+ factorsAllowed: [
+ MfaFactorType.OTP,
+ MfaFactorType.SMS,
+ MfaFactorType.EMAIL,
+ MfaFactorType.PUSH,
+ MfaFactorType.VOICE,
+ ],
+ })
+ )
+ }
+ disabled={!mfaToken}
+ />
+
+
+
+
+
+ run(() => mfa.enroll({ mfaToken, factorType: MfaFactorType.OTP }))
+ }
+ disabled={!mfaToken}
+ />
+
+ run(() =>
+ mfa.enroll({
+ mfaToken,
+ factorType: MfaFactorType.SMS,
+ phoneNumber,
+ })
+ )
+ }
+ disabled={!mfaToken || !phoneNumber}
+ />
+
+ run(() =>
+ mfa.enroll({
+ mfaToken,
+ factorType: MfaFactorType.VOICE,
+ phoneNumber,
+ })
+ )
+ }
+ disabled={!mfaToken || !phoneNumber}
+ />
+
+ run(() =>
+ mfa.enroll({
+ mfaToken,
+ factorType: MfaFactorType.EMAIL,
+ email: enrollEmail,
+ })
+ )
+ }
+ disabled={!mfaToken || !enrollEmail}
+ />
+
+ run(() => mfa.enroll({ mfaToken, factorType: MfaFactorType.PUSH }))
+ }
+ disabled={!mfaToken}
+ />
+
+
+
+
+
+ run(() => mfa.challenge({ mfaToken, authenticatorId }))
+ }
+ disabled={!mfaToken || !authenticatorId}
+ />
+
+
+
+
+
+
+ run(() => mfa.verify({ mfaToken, otp: otpCode }))}
+ disabled={!mfaToken || !otpCode}
+ />
+
+ run(() =>
+ mfa.verify({
+ mfaToken,
+ oobCode,
+ bindingCode: bindingCode || undefined,
+ })
+ )
+ }
+ disabled={!mfaToken || !oobCode}
+ />
+
+
+
+
+ );
+};
+
+export default MfaHooks;
diff --git a/example/src/features/MyAccountClass.tsx b/example/src/features/MyAccountClass.tsx
new file mode 100644
index 000000000..c392805aa
--- /dev/null
+++ b/example/src/features/MyAccountClass.tsx
@@ -0,0 +1,356 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { PreferredAuthenticationMethods } from 'react-native-auth0';
+import auth0 from '../shared/api';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+import config from '../auth0-configuration';
+import { createPasskey } from '../passkey/PasskeyModule';
+
+// Class-based reference for the My Account API. Same operations as
+// MyAccountHooks.tsx, expressed against the Auth0 class instance. Not imported
+// by the app — kept as a side-by-side reference.
+
+const MY_ACCOUNT_SCOPE =
+ 'read:me:authentication_methods delete:me:authentication_methods update:me:authentication_methods read:me:factors create:me:authentication_methods';
+
+const MyAccountClass = () => {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [accessToken, setAccessToken] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [emailAddress, setEmailAddress] = useState('');
+ const [otpCode, setOtpCode] = useState('');
+ const [methodId, setMethodId] = useState('');
+ const [methodName, setMethodName] = useState('');
+ const [enrollmentState, setEnrollmentState] = useState<{
+ id: string;
+ authSession: string;
+ } | null>(null);
+ const [challengeState, setChallengeState] = useState<{
+ authenticationMethodId: string;
+ authSession: string;
+ authParamsPublicKey: Record;
+ } | null>(null);
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ const getToken = async (): Promise => {
+ if (accessToken) return accessToken;
+ const creds = await auth0.credentialsManager.getApiCredentials(
+ `https://${config.domain}/me/`,
+ MY_ACCOUNT_SCOPE
+ );
+ setAccessToken(creds.accessToken);
+ return creds.accessToken;
+ };
+
+ return (
+
+
+ run(async () => {
+ const creds = await auth0.credentialsManager.getApiCredentials(
+ `https://${config.domain}/me/`,
+ MY_ACCOUNT_SCOPE
+ );
+ setAccessToken(creds.accessToken);
+ return { accessToken: creds.accessToken.substring(0, 20) + '...' };
+ })
+ }
+ />
+
+
+ run(async () => {
+ const token = await getToken();
+ return auth0.myAccount.getFactors({ accessToken: token });
+ })
+ }
+ disabled={!accessToken}
+ />
+
+
+ run(async () => {
+ const token = await getToken();
+ return auth0.myAccount.getAuthenticationMethods({
+ accessToken: token,
+ });
+ })
+ }
+ disabled={!accessToken}
+ />
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ return auth0.myAccount.getAuthenticationMethodById({
+ accessToken: token,
+ id: methodId.trim(),
+ });
+ })
+ }
+ disabled={!accessToken || !methodId}
+ />
+
+
+ run(async () => {
+ const token = await getToken();
+ return auth0.myAccount.updateAuthenticationMethodById({
+ accessToken: token,
+ id: methodId.trim(),
+ name: methodName.trim() || undefined,
+ });
+ })
+ }
+ disabled={!accessToken || !methodId}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ return auth0.myAccount.deleteAuthenticationMethodById({
+ accessToken: token,
+ id: methodId.trim(),
+ });
+ })
+ }
+ disabled={!accessToken || !methodId}
+ />
+
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await auth0.myAccount.enrollPhone({
+ accessToken: token,
+ phoneNumber: phoneNumber.trim(),
+ preferredAuthenticationMethod:
+ PreferredAuthenticationMethods.SMS,
+ });
+ setEnrollmentState(challenge);
+ return challenge;
+ })
+ }
+ disabled={!accessToken || !phoneNumber}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const method = await auth0.myAccount.confirmPhoneEnrollment({
+ accessToken: token,
+ id: enrollmentState!.id,
+ authSession: enrollmentState!.authSession,
+ otpCode: otpCode.trim(),
+ });
+ setEnrollmentState(null);
+ return method;
+ })
+ }
+ disabled={!accessToken || !enrollmentState || !otpCode}
+ />
+
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await auth0.myAccount.enrollEmail({
+ accessToken: token,
+ emailAddress: emailAddress.trim(),
+ });
+ setEnrollmentState(challenge);
+ return challenge;
+ })
+ }
+ disabled={!accessToken || !emailAddress}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const method = await auth0.myAccount.confirmEmailEnrollment({
+ accessToken: token,
+ id: enrollmentState!.id,
+ authSession: enrollmentState!.authSession,
+ otpCode: otpCode.trim(),
+ });
+ setEnrollmentState(null);
+ return method;
+ })
+ }
+ disabled={!accessToken || !enrollmentState || !otpCode}
+ />
+
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await auth0.myAccount.enrollTOTP({
+ accessToken: token,
+ });
+ setEnrollmentState({
+ id: challenge.id,
+ authSession: challenge.authSession,
+ });
+ return challenge;
+ })
+ }
+ disabled={!accessToken}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const method = await auth0.myAccount.confirmTOTPEnrollment({
+ accessToken: token,
+ id: enrollmentState!.id,
+ authSession: enrollmentState!.authSession,
+ otpCode: otpCode.trim(),
+ });
+ setEnrollmentState(null);
+ return method;
+ })
+ }
+ disabled={!accessToken || !enrollmentState || !otpCode}
+ />
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await auth0.myAccount.enrollRecoveryCode({
+ accessToken: token,
+ });
+ setEnrollmentState({
+ id: challenge.id,
+ authSession: challenge.authSession,
+ });
+ return challenge;
+ })
+ }
+ disabled={!accessToken}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const method =
+ await auth0.myAccount.confirmRecoveryCodeEnrollment({
+ accessToken: token,
+ id: enrollmentState!.id,
+ authSession: enrollmentState!.authSession,
+ });
+ setEnrollmentState(null);
+ return method;
+ })
+ }
+ disabled={!accessToken || !enrollmentState}
+ />
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge =
+ await auth0.myAccount.passkeyEnrollmentChallenge({
+ accessToken: token,
+ });
+ setChallengeState(challenge);
+ return challenge;
+ })
+ }
+ disabled={!accessToken}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const authResponse = await createPasskey(
+ challengeState!.authParamsPublicKey
+ );
+ return auth0.myAccount.enrollPasskey({
+ accessToken: token,
+ authenticationMethodId: challengeState!.authenticationMethodId,
+ authSession: challengeState!.authSession,
+ authParamsPublicKey: challengeState!.authParamsPublicKey,
+ authResponse,
+ });
+ })
+ }
+ disabled={!accessToken || !challengeState}
+ />
+
+
+
+
+ );
+};
+
+export default MyAccountClass;
diff --git a/example/src/features/MyAccountHooks.tsx b/example/src/features/MyAccountHooks.tsx
new file mode 100644
index 000000000..0ed10355b
--- /dev/null
+++ b/example/src/features/MyAccountHooks.tsx
@@ -0,0 +1,349 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { useAuth0, PreferredAuthenticationMethods } from 'react-native-auth0';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+import config from '../auth0-configuration';
+import { createPasskey } from '../passkey/PasskeyModule';
+
+const MY_ACCOUNT_SCOPE =
+ 'read:me:authentication_methods delete:me:authentication_methods update:me:authentication_methods read:me:factors create:me:authentication_methods';
+
+const MyAccountHooks = () => {
+ const { getApiCredentials, myAccount } = useAuth0();
+
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [accessToken, setAccessToken] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [emailAddress, setEmailAddress] = useState('');
+ const [otpCode, setOtpCode] = useState('');
+ const [methodId, setMethodId] = useState('');
+ const [methodName, setMethodName] = useState('');
+ const [enrollmentState, setEnrollmentState] = useState<{
+ id: string;
+ authSession: string;
+ } | null>(null);
+ const [challengeState, setChallengeState] = useState<{
+ authenticationMethodId: string;
+ authSession: string;
+ authParamsPublicKey: Record;
+ } | null>(null);
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ const getToken = async (): Promise => {
+ if (accessToken) return accessToken;
+ const creds = await getApiCredentials(
+ `https://${config.domain}/me/`,
+ MY_ACCOUNT_SCOPE
+ );
+ setAccessToken(creds.accessToken);
+ return creds.accessToken;
+ };
+
+ return (
+
+
+ run(async () => {
+ const creds = await getApiCredentials(
+ `https://${config.domain}/me/`,
+ MY_ACCOUNT_SCOPE
+ );
+ setAccessToken(creds.accessToken);
+ return { accessToken: creds.accessToken.substring(0, 20) + '...' };
+ })
+ }
+ />
+
+
+ run(async () => {
+ const token = await getToken();
+ return myAccount.getFactors({ accessToken: token });
+ })
+ }
+ disabled={!accessToken}
+ />
+
+
+ run(async () => {
+ const token = await getToken();
+ return myAccount.getAuthenticationMethods({ accessToken: token });
+ })
+ }
+ disabled={!accessToken}
+ />
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ return myAccount.getAuthenticationMethodById({
+ accessToken: token,
+ id: methodId.trim(),
+ });
+ })
+ }
+ disabled={!accessToken || !methodId}
+ />
+
+
+ run(async () => {
+ const token = await getToken();
+ return myAccount.updateAuthenticationMethodById({
+ accessToken: token,
+ id: methodId.trim(),
+ name: methodName.trim() || undefined,
+ });
+ })
+ }
+ disabled={!accessToken || !methodId}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ return myAccount.deleteAuthenticationMethodById({
+ accessToken: token,
+ id: methodId.trim(),
+ });
+ })
+ }
+ disabled={!accessToken || !methodId}
+ />
+
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await myAccount.enrollPhone({
+ accessToken: token,
+ phoneNumber: phoneNumber.trim(),
+ preferredAuthenticationMethod:
+ PreferredAuthenticationMethods.SMS,
+ });
+ setEnrollmentState(challenge);
+ return challenge;
+ })
+ }
+ disabled={!accessToken || !phoneNumber}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const method = await myAccount.confirmPhoneEnrollment({
+ accessToken: token,
+ id: enrollmentState!.id,
+ authSession: enrollmentState!.authSession,
+ otpCode: otpCode.trim(),
+ });
+ setEnrollmentState(null);
+ return method;
+ })
+ }
+ disabled={!accessToken || !enrollmentState || !otpCode}
+ />
+
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await myAccount.enrollEmail({
+ accessToken: token,
+ emailAddress: emailAddress.trim(),
+ });
+ setEnrollmentState(challenge);
+ return challenge;
+ })
+ }
+ disabled={!accessToken || !emailAddress}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const method = await myAccount.confirmEmailEnrollment({
+ accessToken: token,
+ id: enrollmentState!.id,
+ authSession: enrollmentState!.authSession,
+ otpCode: otpCode.trim(),
+ });
+ setEnrollmentState(null);
+ return method;
+ })
+ }
+ disabled={!accessToken || !enrollmentState || !otpCode}
+ />
+
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await myAccount.enrollTOTP({
+ accessToken: token,
+ });
+ setEnrollmentState({
+ id: challenge.id,
+ authSession: challenge.authSession,
+ });
+ return challenge;
+ })
+ }
+ disabled={!accessToken}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const method = await myAccount.confirmTOTPEnrollment({
+ accessToken: token,
+ id: enrollmentState!.id,
+ authSession: enrollmentState!.authSession,
+ otpCode: otpCode.trim(),
+ });
+ setEnrollmentState(null);
+ return method;
+ })
+ }
+ disabled={!accessToken || !enrollmentState || !otpCode}
+ />
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await myAccount.enrollRecoveryCode({
+ accessToken: token,
+ });
+ setEnrollmentState({
+ id: challenge.id,
+ authSession: challenge.authSession,
+ });
+ return challenge;
+ })
+ }
+ disabled={!accessToken}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const method = await myAccount.confirmRecoveryCodeEnrollment({
+ accessToken: token,
+ id: enrollmentState!.id,
+ authSession: enrollmentState!.authSession,
+ });
+ setEnrollmentState(null);
+ return method;
+ })
+ }
+ disabled={!accessToken || !enrollmentState}
+ />
+
+
+
+
+ run(async () => {
+ const token = await getToken();
+ const challenge = await myAccount.passkeyEnrollmentChallenge({
+ accessToken: token,
+ });
+ setChallengeState(challenge);
+ return challenge;
+ })
+ }
+ disabled={!accessToken}
+ />
+
+ run(async () => {
+ const token = await getToken();
+ const authResponse = await createPasskey(
+ challengeState!.authParamsPublicKey
+ );
+ return myAccount.enrollPasskey({
+ accessToken: token,
+ authenticationMethodId: challengeState!.authenticationMethodId,
+ authSession: challengeState!.authSession,
+ authParamsPublicKey: challengeState!.authParamsPublicKey,
+ authResponse,
+ });
+ })
+ }
+ disabled={!accessToken || !challengeState}
+ />
+
+
+
+
+ );
+};
+
+export default MyAccountHooks;
diff --git a/example/src/features/PasskeysClass.tsx b/example/src/features/PasskeysClass.tsx
new file mode 100644
index 000000000..fb5900368
--- /dev/null
+++ b/example/src/features/PasskeysClass.tsx
@@ -0,0 +1,79 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import auth0 from '../shared/api';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+import { createPasskey, getPasskey } from '../passkey/PasskeyModule';
+
+// Class-based reference for Passkeys. Same operations as PasskeysHooks.tsx,
+// expressed against the Auth0 class instance. Not imported by the app — kept
+// as a side-by-side reference.
+const PasskeysClass = () => {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [email, setEmail] = useState('');
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+
+
+ run(async () => {
+ const challenge = await auth0.passkeySignupChallenge({
+ email: email || undefined,
+ realm: 'Username-Password-Authentication',
+ });
+ const credentialJson = await createPasskey(
+ challenge.authParamsPublicKey
+ );
+ return auth0.getTokenByPasskey({
+ authSession: challenge.authSession,
+ authResponse: credentialJson,
+ realm: 'Username-Password-Authentication',
+ });
+ })
+ }
+ />
+
+
+ run(async () => {
+ const challenge = await auth0.passkeyLoginChallenge({
+ realm: 'Username-Password-Authentication',
+ });
+ const credentialJson = await getPasskey(
+ challenge.authParamsPublicKey
+ );
+ return auth0.getTokenByPasskey({
+ authSession: challenge.authSession,
+ authResponse: credentialJson,
+ realm: 'Username-Password-Authentication',
+ });
+ })
+ }
+ />
+
+
+ );
+};
+
+export default PasskeysClass;
diff --git a/example/src/features/PasskeysHooks.tsx b/example/src/features/PasskeysHooks.tsx
new file mode 100644
index 000000000..68d613334
--- /dev/null
+++ b/example/src/features/PasskeysHooks.tsx
@@ -0,0 +1,79 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { useAuth0 } from 'react-native-auth0';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+import { createPasskey, getPasskey } from '../passkey/PasskeyModule';
+
+const PasskeysHooks = () => {
+ const { passkeySignupChallenge, passkeyLoginChallenge, getTokenByPasskey } =
+ useAuth0();
+
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [email, setEmail] = useState('');
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+
+
+
+ run(async () => {
+ const challenge = await passkeySignupChallenge({
+ email: email || undefined,
+ realm: 'Username-Password-Authentication',
+ });
+ const credentialJson = await createPasskey(
+ challenge.authParamsPublicKey
+ );
+ return getTokenByPasskey({
+ authSession: challenge.authSession,
+ authResponse: credentialJson,
+ realm: 'Username-Password-Authentication',
+ });
+ })
+ }
+ />
+
+
+ run(async () => {
+ const challenge = await passkeyLoginChallenge({
+ realm: 'Username-Password-Authentication',
+ });
+ const credentialJson = await getPasskey(
+ challenge.authParamsPublicKey
+ );
+ return getTokenByPasskey({
+ authSession: challenge.authSession,
+ authResponse: credentialJson,
+ realm: 'Username-Password-Authentication',
+ });
+ })
+ }
+ />
+
+
+ );
+};
+
+export default PasskeysHooks;
diff --git a/example/src/features/PasswordlessClass.tsx b/example/src/features/PasswordlessClass.tsx
new file mode 100644
index 000000000..329fe9759
--- /dev/null
+++ b/example/src/features/PasswordlessClass.tsx
@@ -0,0 +1,151 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import type { PasswordlessChallenge } from 'react-native-auth0';
+import auth0 from '../shared/api';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+// Class-based reference for Passwordless. Same operations as
+// PasswordlessHooks.tsx, expressed against the Auth0 class instance. Not
+// imported by the app — kept as a side-by-side reference.
+const PasswordlessClass = () => {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [email, setEmail] = useState('');
+ const [emailCode, setEmailCode] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [smsCode, setSmsCode] = useState('');
+ const [otpCode, setOtpCode] = useState('');
+ const [challenge, setChallenge] = useState(
+ null
+ );
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+ {/* Email code flow */}
+
+
+
+ run(() => auth0.auth.passwordlessWithEmail({ email, send: 'code' }))
+ }
+ disabled={!email}
+ />
+
+
+ run(() => auth0.auth.loginWithEmail({ email, code: emailCode }))
+ }
+ disabled={!email || !emailCode}
+ />
+
+
+ {/* SMS code flow */}
+
+
+
+ run(() =>
+ auth0.auth.passwordlessWithSMS({ phoneNumber, send: 'code' })
+ )
+ }
+ disabled={!phoneNumber}
+ />
+
+
+ run(() => auth0.auth.loginWithSMS({ phoneNumber, code: smsCode }))
+ }
+ disabled={!phoneNumber || !smsCode}
+ />
+
+
+ {/* DB connection OTP flow (passwordless.*) */}
+
+
+ run(async () => {
+ const ch = await auth0.passwordless.challengeWithEmail({
+ email,
+ connection: 'Username-Password-Authentication',
+ allowSignup: true,
+ });
+ setChallenge(ch);
+ return ch;
+ })
+ }
+ disabled={!email}
+ />
+
+ run(async () => {
+ const ch = await auth0.passwordless.challengeWithPhoneNumber({
+ phoneNumber,
+ connection: 'Username-Password-Authentication',
+ deliveryMethod: 'text',
+ allowSignup: true,
+ });
+ setChallenge(ch);
+ return ch;
+ })
+ }
+ disabled={!phoneNumber}
+ />
+
+
+ run(() =>
+ auth0.passwordless.loginWithOTP({
+ challenge: challenge!,
+ otp: otpCode,
+ })
+ )
+ }
+ disabled={!challenge || !otpCode}
+ />
+
+
+
+
+ );
+};
+
+export default PasswordlessClass;
diff --git a/example/src/features/PasswordlessHooks.tsx b/example/src/features/PasswordlessHooks.tsx
new file mode 100644
index 000000000..43f8ddf09
--- /dev/null
+++ b/example/src/features/PasswordlessHooks.tsx
@@ -0,0 +1,150 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { useAuth0 } from 'react-native-auth0';
+import type { PasswordlessChallenge } from 'react-native-auth0';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+const PasswordlessHooks = () => {
+ const {
+ sendEmailCode,
+ authorizeWithEmail,
+ sendSMSCode,
+ authorizeWithSMS,
+ passwordless,
+ } = useAuth0();
+
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [email, setEmail] = useState('');
+ const [emailCode, setEmailCode] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [smsCode, setSmsCode] = useState('');
+ const [otpCode, setOtpCode] = useState('');
+ const [challenge, setChallenge] = useState(
+ null
+ );
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+ {/* Email code flow */}
+
+
+ run(() => sendEmailCode({ email }))}
+ disabled={!email}
+ />
+
+
+ run(() => authorizeWithEmail({ email, code: emailCode }))
+ }
+ disabled={!email || !emailCode}
+ />
+
+
+ {/* SMS code flow */}
+
+
+ run(() => sendSMSCode({ phoneNumber }))}
+ disabled={!phoneNumber}
+ />
+
+
+ run(() => authorizeWithSMS({ phoneNumber, code: smsCode }))
+ }
+ disabled={!phoneNumber || !smsCode}
+ />
+
+
+ {/* DB connection OTP flow (passwordless.*) */}
+
+
+ run(async () => {
+ const ch = await passwordless.challengeWithEmail({
+ email,
+ connection: 'Username-Password-Authentication',
+ allowSignup: true,
+ });
+ setChallenge(ch);
+ return ch;
+ })
+ }
+ disabled={!email}
+ />
+
+ run(async () => {
+ const ch = await passwordless.challengeWithPhoneNumber({
+ phoneNumber,
+ connection: 'Username-Password-Authentication',
+ deliveryMethod: 'text',
+ allowSignup: true,
+ });
+ setChallenge(ch);
+ return ch;
+ })
+ }
+ disabled={!phoneNumber}
+ />
+
+
+ run(() =>
+ passwordless.loginWithOTP({
+ challenge: challenge!,
+ otp: otpCode,
+ })
+ )
+ }
+ disabled={!challenge || !otpCode}
+ />
+
+
+
+
+ );
+};
+
+export default PasswordlessHooks;
diff --git a/example/src/features/WebAuthClass.tsx b/example/src/features/WebAuthClass.tsx
new file mode 100644
index 000000000..627624829
--- /dev/null
+++ b/example/src/features/WebAuthClass.tsx
@@ -0,0 +1,79 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import auth0 from '../shared/api';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+// Class-based reference for Web Authentication. Same operations as
+// WebAuthHooks.tsx, expressed against the Auth0 class instance. Not imported
+// by the app — kept as a side-by-side reference.
+const WebAuthClass = () => {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [organization, setOrganization] = useState('');
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+ run(() => auth0.webAuth.authorize({}))}
+ />
+
+
+ run(() => auth0.webAuth.authorize({ organization }))}
+ disabled={!organization}
+ />
+
+
+ run(() => auth0.webAuth.authorize({}, { ephemeralSession: true }))
+ }
+ />
+ run(() => auth0.webAuth.clearSession())}
+ />
+ run(() => auth0.webAuth.cancelWebAuth())}
+ />
+ run(() => auth0.webAuth.resumeSession())}
+ />
+ run(() => auth0.webAuth.getWebUser())}
+ />
+ run(() => auth0.webAuth.checkWebSession())}
+ />
+ run(() => auth0.webAuth.handleRedirectCallback())}
+ />
+
+
+ );
+};
+
+export default WebAuthClass;
diff --git a/example/src/features/WebAuthHooks.tsx b/example/src/features/WebAuthHooks.tsx
new file mode 100644
index 000000000..33a150590
--- /dev/null
+++ b/example/src/features/WebAuthHooks.tsx
@@ -0,0 +1,79 @@
+import React, { useState } from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { useAuth0 } from 'react-native-auth0';
+import Section from '../shared/Section';
+import ResultView from '../shared/ResultView';
+
+const WebAuthHooks = () => {
+ const {
+ authorize,
+ clearSession,
+ cancelWebAuth,
+ resumeSession,
+ user,
+ isLoading,
+ } = useAuth0();
+
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+ const [organization, setOrganization] = useState('');
+
+ const run = async (fn: () => Promise) => {
+ setError(null);
+ setResult(null);
+ try {
+ const res = await fn();
+ setResult(res ?? { success: true });
+ } catch (e) {
+ setError(e as Error);
+ }
+ };
+
+ return (
+
+ run(() => authorize())}
+ />
+
+
+ run(() => authorize({ organization }))}
+ disabled={!organization}
+ />
+
+ run(() => authorize({}, { ephemeralSession: true }))}
+ />
+ run(() => clearSession())}
+ />
+ run(() => cancelWebAuth())}
+ />
+ run(() => resumeSession())}
+ />
+ {
+ setError(null);
+ setResult(user);
+ }}
+ disabled={isLoading}
+ />
+
+
+ );
+};
+
+export default WebAuthHooks;
diff --git a/example/src/navigation/AuthStackNavigator.tsx b/example/src/navigation/AuthStackNavigator.tsx
deleted file mode 100644
index 72ebc5f1f..000000000
--- a/example/src/navigation/AuthStackNavigator.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-// example/src/navigation/AuthStackNavigator.tsx
-
-import React from 'react';
-import { createStackNavigator } from '@react-navigation/stack';
-import HomeScreen from '../screens/hooks/Home';
-
-export type AuthStackParamList = {
- Home: undefined;
-};
-
-const Stack = createStackNavigator();
-
-/**
- * Navigator for the unauthenticated part of the Hooks-based demo.
- * It displays the main login screen.
- */
-const AuthStackNavigator = () => {
- return (
-
-
-
- );
-};
-
-export default AuthStackNavigator;
diff --git a/example/src/navigation/ClassDemoNavigator.tsx b/example/src/navigation/ClassDemoNavigator.tsx
deleted file mode 100644
index 475f7c0ec..000000000
--- a/example/src/navigation/ClassDemoNavigator.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React from 'react';
-import { createStackNavigator } from '@react-navigation/stack';
-import ClassLoginScreen from '../screens/class-based/ClassLogin';
-import ClassProfileScreen from '../screens/class-based/ClassProfile';
-import ClassApiTestsScreen from '../screens/class-based/ClassApiTests';
-import type { Credentials } from 'react-native-auth0';
-
-/**
- * Defines the screens and their parameters for the class-based navigation stack.
- * This provides type safety for navigation calls and route props.
- */
-export type ClassDemoStackParamList = {
- ClassLogin: undefined;
- ClassProfile: { credentials: Credentials }; // Expects credentials to be passed after login
- ClassApiTests: { accessToken: string; idToken?: string }; // Access token for API calls; idToken prefills the CTE actor token
-};
-
-const Stack = createStackNavigator();
-
-/**
- * The navigator for the entire Class-based demo flow.
- *
- * It does NOT use an Auth0Provider, demonstrating how to use the SDK
- * by importing and calling the Auth0 class instance directly.
- */
-const ClassDemoNavigator = () => {
- return (
-
-
-
-
-
- );
-};
-
-export default ClassDemoNavigator;
diff --git a/example/src/navigation/HooksDemoNavigator.tsx b/example/src/navigation/HooksDemoNavigator.tsx
deleted file mode 100644
index b7e0d73c4..000000000
--- a/example/src/navigation/HooksDemoNavigator.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import React, { useEffect } from 'react';
-import {
- Auth0Provider,
- useAuth0,
- BiometricPolicy,
- LocalAuthenticationStrategy,
- LocalAuthenticationLevel,
-} from 'react-native-auth0';
-import AuthStackNavigator from './AuthStackNavigator';
-import MainTabNavigator from './MainTabNavigator';
-import { ActivityIndicator, View, StyleSheet, Platform } from 'react-native';
-import config from '../auth0-configuration';
-
-const AUTH0_DOMAIN = config.domain;
-const AUTH0_CLIENT_ID = config.clientId;
-
-/**
- * A helper component that contains the logic to switch between the
- * authentication stack and the main application stack based on user state.
- * It's rendered inside the Auth0Provider so it can use the useAuth0 hook.
- */
-const AppContent = () => {
- const { user, isLoading, resumeSession } = useAuth0();
-
- // On Android the OS can kill the app process while the user is completing
- // login in the browser. When the app cold-starts, resumeSession() recovers
- // any login that finished after the process was killed. It is a safe no-op
- // that resolves null on iOS and web, so it can be called unconditionally.
- useEffect(() => {
- if (Platform.OS !== 'android') {
- return;
- }
- resumeSession()
- .then((credentials) => {
- if (credentials) {
- console.log('Recovered login after process death');
- }
- })
- .catch((e) => {
- console.warn('resumeSession failed', e);
- });
- }, [resumeSession]);
-
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- // If user is authenticated, show the main app, otherwise show the login screen.
- return user ? : ;
-};
-
-/**
- * This component wraps the entire Hooks-based demo flow with the Auth0Provider,
- * making the authentication context available to all its child screens.
- *
- * Biometric Policy Examples:
- * - BiometricPolicy.default: System-managed, may skip prompt if recently authenticated
- * - BiometricPolicy.always: Always shows biometric prompt on every credential access
- * - BiometricPolicy.session: Shows prompt once, then caches for specified timeout
- * - BiometricPolicy.appLifecycle: Shows prompt once per app lifecycle
- *
- * Uncomment different policies below to test them.
- */
-const HooksDemoNavigator = () => {
- return (
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- },
-});
-
-export default HooksDemoNavigator;
diff --git a/example/src/navigation/MainTabNavigator.tsx b/example/src/navigation/MainTabNavigator.tsx
deleted file mode 100644
index f470fdf82..000000000
--- a/example/src/navigation/MainTabNavigator.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-// example/src/navigation/MainTabNavigator.tsx
-
-import React from 'react';
-import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
-import ProfileScreen from '../screens/hooks/Profile';
-import MoreScreen from '../screens/hooks/More';
-import CredentialsScreen from '../screens/hooks/CredentialsScreen';
-import MyAccountScreen from '../screens/hooks/MyAccountScreen';
-
-export type MainTabParamList = {
- Profile: undefined;
- Api: undefined;
- More: undefined;
- Credentials: undefined;
- MyAccount: undefined;
-};
-
-const Tab = createBottomTabNavigator();
-
-/**
- * Navigator for the authenticated part of the Hooks-based demo.
- * It provides tab-based navigation to the Profile, API, and other screens.
- */
-const MainTabNavigator = () => {
- return (
-
-
-
-
-
-
- );
-};
-
-export default MainTabNavigator;
diff --git a/example/src/navigation/RootNavigator.tsx b/example/src/navigation/RootNavigator.tsx
deleted file mode 100644
index 00b53c7ec..000000000
--- a/example/src/navigation/RootNavigator.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-// example/src/navigation/RootNavigator.tsx
-
-import React, { Suspense } from 'react';
-import { ActivityIndicator, View, StyleSheet } from 'react-native';
-import { createStackNavigator } from '@react-navigation/stack';
-import SelectionScreen from '../screens/SelectionScreen';
-
-// Lazy load the demo navigators to prevent Auth0Provider from initializing
-// until the user actually navigates to those screens.
-const HooksDemoNavigator = React.lazy(() => import('./HooksDemoNavigator'));
-const ClassDemoNavigator = React.lazy(() => import('./ClassDemoNavigator'));
-
-// Define the parameter list for type safety
-export type RootStackParamList = {
- Selection: undefined;
- HooksDemo: undefined;
- ClassDemo: undefined;
-};
-
-const Stack = createStackNavigator();
-
-// Loading fallback component
-const LoadingFallback = () => (
-
-
-
-);
-
-/**
- * The top-level navigator that allows the user to select which
- * demo they want to see: the recommended Hooks-based approach or
- * the class-based approach.
- */
-const RootNavigator = () => {
- return (
-
-
-
- {() => (
- }>
-
-
- )}
-
-
- {() => (
- }>
-
-
- )}
-
-
- );
-};
-
-const styles = StyleSheet.create({
- loadingContainer: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: '#FFFFFF',
- },
-});
-
-export default RootNavigator;
diff --git a/example/src/screens/SelectionScreen.tsx b/example/src/screens/SelectionScreen.tsx
deleted file mode 100644
index 9c84c016a..000000000
--- a/example/src/screens/SelectionScreen.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-// example/src/screens/SelectionScreen.tsx
-
-import React from 'react';
-import { View, Text, StyleSheet } from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import { useNavigation } from '@react-navigation/native';
-import type { StackNavigationProp } from '@react-navigation/stack';
-import Button from '../components/Button';
-import Header from '../components/Header';
-import type { RootStackParamList } from '../navigation/RootNavigator';
-
-// Use the specific navigation prop type from our RootNavigator's param list
-// for type-safe navigation.
-type SelectionScreenNavigationProp = StackNavigationProp<
- RootStackParamList,
- 'Selection'
->;
-
-/**
- * The initial screen of the application. It allows the user to navigate
- * to either the Hooks-based demo or the Class-based demo.
- */
-const SelectionScreen = () => {
- const navigation = useNavigation();
-
- return (
-
-
-
-
- Choose a demonstration to see the Auth0 SDK in action.
-
-
- navigation.navigate('HooksDemo')}
- title="Hooks Demo (Recommended)"
- />
-
-
-
- navigation.navigate('ClassDemo')}
- title="Class-Based Demo"
- style={styles.secondaryButton}
- textStyle={styles.secondaryButtonText}
- />
-
-
- The Hooks demo shows the recommended integration for modern React
- Native apps. The Class-based demo is for testing direct API calls.
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#FFFFFF',
- },
- content: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- paddingHorizontal: 24,
- },
- description: {
- fontSize: 18,
- textAlign: 'center',
- color: '#424242',
- marginBottom: 40,
- },
- spacer: {
- height: 20,
- },
- secondaryButton: {
- backgroundColor: '#FFFFFF',
- borderWidth: 2,
- borderColor: '#E53935',
- },
- secondaryButtonText: {
- color: '#E53935',
- },
- footer: {
- position: 'absolute',
- bottom: 30,
- fontSize: 14,
- textAlign: 'center',
- color: '#757575',
- paddingHorizontal: 20,
- },
-});
-
-export default SelectionScreen;
diff --git a/example/src/screens/class-based/ClassApiTests.tsx b/example/src/screens/class-based/ClassApiTests.tsx
deleted file mode 100644
index 73eaf6e6b..000000000
--- a/example/src/screens/class-based/ClassApiTests.tsx
+++ /dev/null
@@ -1,312 +0,0 @@
-import React, { useState } from 'react';
-import { ScrollView, StyleSheet, Text, View } from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import { RouteProp } from '@react-navigation/native';
-import auth0 from '../../api/auth0';
-import Button from '../../components/Button';
-import Header from '../../components/Header';
-import Result from '../../components/Result';
-import LabeledInput from '../../components/LabeledInput';
-import type { ClassDemoStackParamList } from '../../navigation/ClassDemoNavigator';
-
-type ApiTestsRouteProp = RouteProp;
-
-type Props = {
- route: ApiTestsRouteProp;
-};
-
-const ClassApiTestsScreen = ({ route }: Props) => {
- const { accessToken, idToken } = route.params;
- const [result, setResult] = useState(null);
- const [error, setError] = useState(null);
-
- // State for specific API calls
- const [email, setEmail] = useState('test-user@auth0.com'); // dummy username
- const [password, setPassword] = useState('P@ssword123'); // dummy password
- const [mfaToken, setMfaToken] = useState('');
- const [otp, setOtp] = useState('');
- const [refreshToken, setRefreshToken] = useState('');
-
- // State for Custom Token Exchange (RFC 8693)
- const [subjectToken, setSubjectToken] = useState('');
- const [subjectTokenType, setSubjectTokenType] = useState(
- 'urn:acme:external-idp-token'
- );
- const [actorToken, setActorToken] = useState(idToken ?? '');
- const [actorTokenType, setActorTokenType] = useState(
- 'urn:ietf:params:oauth:token-type:id_token'
- );
-
- const runTest = async (testFn: () => Promise, title: string) => {
- setError(null);
- setResult(null);
- console.log(`Running test: ${title}`);
- try {
- const res = await testFn();
- const successMessage = res ?? {
- success: `${title} completed successfully`,
- };
- console.log('Success:', successMessage);
- setResult(successMessage);
-
- // If we got credentials, update our state for subsequent tests
- if (res?.mfa_token) setMfaToken(res.mfa_token);
- if (res?.refreshToken) setRefreshToken(res.refreshToken);
- } catch (e) {
- console.log('Error:', e);
- setError(e as Error);
- }
- };
-
- return (
-
-
-
-
-
-
-
- runTest(
- () => auth0.auth.userInfo({ token: accessToken }),
- 'Get User Info'
- )
- }
- title="auth.userInfo()"
- />
-
-
-
-
-
- runTest(
- () => auth0.auth.passwordlessWithEmail({ email, send: 'code' }),
- 'Passwordless Email'
- )
- }
- title="auth.passwordlessWithEmail(code)"
- />
- {/* Note: loginWithEmail would require getting the code from the email */}
-
-
-
-
-
-
- runTest(
- () =>
- auth0.auth.createUser({
- email,
- password,
- connection: 'Username-Password-Authentication',
- }),
- 'Create User'
- )
- }
- title="auth.createUser()"
- />
-
- runTest(
- () =>
- auth0.auth.resetPassword({
- email,
- connection: 'Username-Password-Authentication',
- }),
- 'Reset Password'
- )
- }
- title="auth.resetPassword()"
- />
-
-
-
-
-
-
- runTest(
- () => auth0.mfa.verify({ mfaToken, otp }),
- 'Verify MFA with OTP'
- )
- }
- title="mfa.verify()"
- disabled={!mfaToken || !otp}
- />
-
-
- runTest(
- () => auth0.auth.refreshToken({ refreshToken }),
- 'Refresh Token'
- )
- }
- title="auth.refreshToken()"
- disabled={!refreshToken}
- />
-
- runTest(
- () => auth0.auth.revoke({ refreshToken }),
- 'Revoke Refresh Token'
- )
- }
- title="auth.revoke()"
- disabled={!refreshToken}
- />
-
-
-
-
-
-
- runTest(
- () =>
- auth0.customTokenExchange({
- subjectToken,
- subjectTokenType,
- }),
- 'Custom Token Exchange'
- )
- }
- title="customTokenExchange()"
- disabled={!subjectToken || !subjectTokenType}
- />
-
- Delegation & Impersonation
-
- idToken && setActorToken(idToken)}
- title="Use my ID token as actor"
- disabled={!idToken}
- />
-
-
- runTest(
- () =>
- auth0.customTokenExchange({
- subjectToken,
- subjectTokenType,
- actorToken,
- actorTokenType,
- }),
- 'Custom Token Exchange (with Actor)'
- )
- }
- title="customTokenExchange() with actor"
- disabled={
- !subjectToken ||
- !subjectTokenType ||
- !actorToken ||
- !actorTokenType
- }
- />
-
-
-
- );
-};
-
-const Section = ({
- title,
- children,
-}: {
- title: string;
- children: React.ReactNode;
-}) => (
-
- {title}
- {children}
-
-);
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#FFFFFF',
- },
- content: {
- padding: 16,
- paddingBottom: 50,
- },
- section: {
- marginBottom: 20,
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- padding: 16,
- },
- sectionTitle: {
- fontSize: 18,
- fontWeight: 'bold',
- marginBottom: 12,
- },
- subheading: {
- fontSize: 14,
- fontWeight: '600',
- marginTop: 8,
- marginBottom: 4,
- color: '#555555',
- },
- buttonGroup: {
- gap: 10,
- },
-});
-
-export default ClassApiTestsScreen;
diff --git a/example/src/screens/class-based/ClassLogin.tsx b/example/src/screens/class-based/ClassLogin.tsx
deleted file mode 100644
index c043b2353..000000000
--- a/example/src/screens/class-based/ClassLogin.tsx
+++ /dev/null
@@ -1,659 +0,0 @@
-import React, { useState } from 'react';
-import {
- ScrollView,
- View,
- Text,
- StyleSheet,
- Alert,
- Platform,
-} from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import { useNavigation } from '@react-navigation/native';
-import type { StackNavigationProp } from '@react-navigation/stack';
-import {
- MfaError,
- MfaErrorCodes,
- MfaFactorType,
- WebAuthError,
- WebAuthErrorCodes,
-} from 'react-native-auth0';
-import type { PasswordlessChallenge } from 'react-native-auth0';
-import auth0 from '../../api/auth0';
-import Button from '../../components/Button';
-import Header from '../../components/Header';
-import LabeledInput from '../../components/LabeledInput';
-import Result from '../../components/Result';
-import type { ClassDemoStackParamList } from '../../navigation/ClassDemoNavigator';
-import config from '../../auth0-configuration';
-
-type NavigationProp = StackNavigationProp<
- ClassDemoStackParamList,
- 'ClassLogin'
->;
-
-const ClassLoginScreen = () => {
- const [error, setError] = useState(null);
- const [result, setResult] = useState(null);
- const [loading, setLoading] = useState(false);
- const navigation = useNavigation();
-
- const [email, setEmail] = useState('');
- const [password, setPassword] = useState('');
-
- const [useTrustedWebActivity, setUseTrustedWebActivity] = useState(false);
- const [ephemeralSession, setEphemeralSession] = useState(false);
-
- // MFA state (flat API-test panel)
- const [mfaToken, setMfaToken] = useState('');
- const [mfaOtp, setMfaOtp] = useState('');
- const [authenticatorId, setAuthenticatorId] = useState('');
- const [enrollPhone, setEnrollPhone] = useState('');
- const [enrollEmailMfa, setEnrollEmailMfa] = useState('');
- const [oobCode, setOobCode] = useState('');
- const [bindingCode, setBindingCode] = useState('');
- const [recoveryCode, setRecoveryCode] = useState('');
- const [verifyScope, setVerifyScope] = useState('');
- const [verifyAudience, setVerifyAudience] = useState('');
- const [mfaLoading, setMfaLoading] = useState(false);
-
- // Passwordless OTP state
- const [otpMethod, setOtpMethod] = useState<'email' | 'phone'>('email');
- const [otpEmail, setOtpEmail] = useState('');
- const [otpPhone, setOtpPhone] = useState('');
- const [otp, setOtp] = useState('');
- const [challenge, setChallenge] = useState(
- null
- );
-
- const onLogin = async () => {
- setLoading(true);
- setError(null);
- try {
- const credentials = await auth0.webAuth.authorize(
- {
- scope: 'openid profile email offline_access',
- audience: `https://${config.domain}/api/v2/`,
- },
- { useTrustedWebActivity, ephemeralSession }
- );
- // On success, we save the credentials and navigate to the profile screen.
- await auth0.credentialsManager.saveCredentials(credentials);
- navigation.replace('ClassProfile', { credentials });
- } catch (e) {
- if (e instanceof WebAuthError) {
- switch (e.type) {
- case WebAuthErrorCodes.USER_CANCELLED:
- Alert.alert('Login Cancelled', 'You cancelled the login process.');
- break;
- case WebAuthErrorCodes.TIMEOUT_ERROR:
- Alert.alert('Login Timeout', 'The login process timed out.');
- break;
- default:
- Alert.alert('Authentication Error', e.message);
- }
- } else {
- setError(e as Error);
- }
- } finally {
- setLoading(false);
- }
- };
-
- const onLoginWithPassword = async () => {
- clearResult();
- try {
- const credentials = await auth0.auth.passwordRealm({
- username: email,
- password: password,
- realm: 'Username-Password-Authentication',
- });
- await auth0.credentialsManager.saveCredentials(credentials);
- navigation.replace('ClassProfile', { credentials });
- } catch (e: any) {
- if (e?.json?.mfa_token) {
- setMfaToken(e.json.mfa_token);
- Alert.alert(
- 'MFA Required',
- 'Multi-factor authentication is required. The MFA token has been filled in below.'
- );
- }
- setError(e as Error);
- }
- };
-
- const clearResult = () => {
- setError(null);
- setResult(null);
- };
-
- const mfaClient = auth0.mfa;
-
- // mfa.verify() returns credentials; redact tokens before they reach the
- // displayed result panel.
- const sanitizeCredentialResult = (res: Record) => {
- const { accessToken, refreshToken, idToken, ...safe } = res;
- return {
- ...safe,
- ...(accessToken ? { accessToken: '[REDACTED]' } : {}),
- ...(idToken ? { idToken: '[REDACTED]' } : {}),
- ...(refreshToken ? { refreshToken: '[REDACTED]' } : {}),
- };
- };
-
- const runMfaTest = async (testFn: () => Promise, title: string) => {
- clearResult();
- setMfaLoading(true);
- try {
- const res = await testFn();
- setResult(res ?? { success: `${title} completed successfully` });
- // Carry an oobCode forward from a challenge/enroll result so verify can
- // use it without manual copy/paste.
- if (res?.oobCode) setOobCode(res.oobCode);
- } catch (e) {
- if (
- e instanceof MfaError &&
- (e.type === MfaErrorCodes.EXPIRED_MFA_TOKEN ||
- e.type === MfaErrorCodes.INVALID_MFA_TOKEN)
- ) {
- Alert.alert(
- 'Session Expired',
- 'Please log in again to get a new MFA token.'
- );
- setMfaToken('');
- }
- setError(e as Error);
- } finally {
- setMfaLoading(false);
- }
- };
-
- const onSendOtpChallenge = async () => {
- setLoading(true);
- setError(null);
- try {
- const result =
- otpMethod === 'email'
- ? await auth0.passwordless.challengeWithEmail({
- email: otpEmail,
- connection: 'Username-Password-Authentication',
- allowSignup: true,
- })
- : await auth0.passwordless.challengeWithPhoneNumber({
- phoneNumber: otpPhone,
- connection: 'Username-Password-Authentication',
- deliveryMethod: 'text',
- allowSignup: true,
- });
- setChallenge(result);
- Alert.alert(
- 'Success',
- otpMethod === 'email'
- ? 'Check your email for the one-time code.'
- : 'Check your phone for the one-time code.'
- );
- } catch (e) {
- setError(e as Error);
- } finally {
- setLoading(false);
- }
- };
-
- const onLoginWithOtp = async () => {
- if (!challenge) {
- return;
- }
- setLoading(true);
- setError(null);
- try {
- const credentials = await auth0.passwordless.loginWithOTP({
- challenge,
- otp,
- audience: `https://${config.domain}/api/v2/`,
- });
- await auth0.credentialsManager.saveCredentials(credentials);
- navigation.replace('ClassProfile', { credentials });
- } catch (e) {
- setError(e as Error);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
-
- {(error || result) && (
-
- )}
-
-
-
- <>
-
- Trusted Web Activity opens login full-screen (no URL bar).
- Requires registering the app's SHA-256 Key Hash in the Auth0
- Dashboard; otherwise it falls back to a Custom Tab. Android only.
-
- {
- setUseTrustedWebActivity((prev) => !prev);
- setEphemeralSession(false);
- }}
- title={`Trusted Web Activity: ${
- useTrustedWebActivity ? 'On' : 'Off'
- }`}
- style={!useTrustedWebActivity ? styles.inactiveButton : undefined}
- />
-
- Ephemeral session disables SSO by running login in an
- incognito-like browser session. On Android this needs Chrome 136+
- and falls back to a regular Custom Tab otherwise.
-
- {
- setEphemeralSession((prev) => !prev);
- setUseTrustedWebActivity(false);
- }}
- title={`Ephemeral Session: ${ephemeralSession ? 'On' : 'Off'}`}
- style={!ephemeralSession ? styles.inactiveButton : undefined}
- />
- >
-
-
-
-
-
-
-
- If MFA is enabled, a failed login returns an mfa_token that is
- filled into the MFA section below.
-
-
-
-
-
- Uses auth0.mfa for flexible MFA operations. Get an mfa_token from a
- failed password login above, or paste one manually.
-
-
-
- runMfaTest(
- () =>
- mfaClient.getAuthenticators({
- mfaToken,
- factorsAllowed: [
- MfaFactorType.OTP,
- MfaFactorType.SMS,
- MfaFactorType.VOICE,
- MfaFactorType.EMAIL,
- MfaFactorType.PUSH,
- 'recovery-code',
- ],
- }),
- 'List Authenticators'
- )
- }
- title="mfa.getAuthenticators()"
- disabled={!mfaToken || mfaLoading}
- />
-
- runMfaTest(
- () =>
- mfaClient.getAuthenticators({
- mfaToken,
- factorsAllowed: ['recovery-code'],
- }),
- 'List Recovery Code Only'
- )
- }
- title="mfa.getAuthenticators(recovery-code)"
- disabled={!mfaToken || mfaLoading}
- />
-
- Each authenticator carries a `type`
- (otp/phone/email/push-notification/recovery-code) alongside
- `authenticatorType` and `oobChannel`. `recovery-code` is listable
- (via `factorsAllowed`) but not enrollable.
-
-
- Enroll a factor
-
- runMfaTest(
- () =>
- mfaClient.enroll({ mfaToken, factorType: MfaFactorType.OTP }),
- 'Enroll TOTP'
- )
- }
- title="mfa.enroll(otp)"
- disabled={!mfaToken || mfaLoading}
- />
-
- runMfaTest(
- () =>
- mfaClient.enroll({
- mfaToken,
- factorType: MfaFactorType.PUSH,
- }),
- 'Enroll Push'
- )
- }
- title="mfa.enroll(push)"
- disabled={!mfaToken || mfaLoading}
- />
-
-
- runMfaTest(
- () =>
- mfaClient.enroll({
- mfaToken,
- factorType: MfaFactorType.SMS,
- phoneNumber: enrollPhone,
- }),
- 'Enroll SMS'
- )
- }
- title="mfa.enroll(sms)"
- disabled={!mfaToken || !enrollPhone || mfaLoading}
- />
-
- runMfaTest(
- () =>
- mfaClient.enroll({
- mfaToken,
- factorType: MfaFactorType.VOICE,
- phoneNumber: enrollPhone,
- }),
- 'Enroll Voice'
- )
- }
- title="mfa.enroll(voice)"
- disabled={!mfaToken || !enrollPhone || mfaLoading}
- />
-
- Voice is a distinct channel on web only. On native it falls back to
- SMS on the same number.
-
-
-
- runMfaTest(
- () =>
- mfaClient.enroll({
- mfaToken,
- factorType: MfaFactorType.EMAIL,
- email: enrollEmailMfa,
- }),
- 'Enroll Email'
- )
- }
- title="mfa.enroll(email)"
- disabled={!mfaToken || !enrollEmailMfa || mfaLoading}
- />
-
- Challenge
-
-
- runMfaTest(
- () => mfaClient.challenge({ mfaToken, authenticatorId }),
- 'Challenge'
- )
- }
- title="mfa.challenge()"
- disabled={!mfaToken || !authenticatorId || mfaLoading}
- />
-
- Verify
-
- Scope/audience are optional; supply them to mint an API access token
- on successful verification.
-
-
-
-
-
- runMfaTest(
- async () =>
- sanitizeCredentialResult(
- (await mfaClient.verify({
- mfaToken,
- otp: mfaOtp,
- scope: verifyScope || undefined,
- audience: verifyAudience || undefined,
- })) as Record
- ),
- 'Verify OTP'
- )
- }
- title="mfa.verify(otp)"
- disabled={!mfaToken || !mfaOtp || mfaLoading}
- />
-
-
-
- runMfaTest(
- async () =>
- sanitizeCredentialResult(
- (await mfaClient.verify({
- mfaToken,
- oobCode,
- bindingCode: bindingCode || undefined,
- scope: verifyScope || undefined,
- audience: verifyAudience || undefined,
- })) as Record
- ),
- 'Verify OOB'
- )
- }
- title="mfa.verify(oob)"
- disabled={!mfaToken || !oobCode || mfaLoading}
- />
-
-
- runMfaTest(
- async () =>
- sanitizeCredentialResult(
- (await mfaClient.verify({
- mfaToken,
- recoveryCode,
- scope: verifyScope || undefined,
- audience: verifyAudience || undefined,
- })) as Record
- ),
- 'Verify Recovery Code'
- )
- }
- title="mfa.verify(recoveryCode)"
- disabled={!mfaToken || !recoveryCode || mfaLoading}
- />
-
-
- {Platform.OS !== 'web' && (
-
-
- Passwordless OTP (Database Connection)
-
-
- Two-step flow on a database connection with email_otp / phone_otp
- enabled: challenge → verify code.
-
-
-
- setOtpMethod('email')}
- title="Email"
- style={[
- styles.halfButton,
- otpMethod !== 'email' && styles.inactiveButton,
- ]}
- />
- setOtpMethod('phone')}
- title="Phone"
- style={[
- styles.halfButton,
- otpMethod !== 'phone' && styles.inactiveButton,
- ]}
- />
-
-
- {otpMethod === 'email' ? (
-
- ) : (
-
- )}
-
-
- {challenge && (
- <>
-
-
- >
- )}
-
- )}
-
-
- );
-};
-
-const Section = ({
- title,
- children,
-}: {
- title: string;
- children: React.ReactNode;
-}) => (
-
- {title}
- {children}
-
-);
-
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#FFFFFF' },
- content: { padding: 16, gap: 20, paddingBottom: 50 },
- section: {
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- padding: 16,
- gap: 10,
- },
- sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 8 },
- subTitle: {
- fontSize: 14,
- fontWeight: '700',
- color: '#333',
- marginTop: 8,
- },
- hint: { fontSize: 12, color: '#888', fontStyle: 'italic' },
- description: { fontSize: 13, color: '#666', marginBottom: 4 },
- row: { flexDirection: 'row', gap: 8 },
- halfButton: { flex: 1, minWidth: 0 },
- inactiveButton: { backgroundColor: '#BDBDBD' },
-});
-
-export default ClassLoginScreen;
diff --git a/example/src/screens/class-based/ClassProfile.tsx b/example/src/screens/class-based/ClassProfile.tsx
deleted file mode 100644
index 6f89385fe..000000000
--- a/example/src/screens/class-based/ClassProfile.tsx
+++ /dev/null
@@ -1,261 +0,0 @@
-import React, { Component } from 'react';
-import {
- ScrollView,
- View,
- StyleSheet,
- Text,
- Alert,
- Linking,
-} from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import { RouteProp, NavigationProp } from '@react-navigation/native';
-import { jwtDecode } from 'jwt-decode';
-import auth0 from '../../api/auth0';
-import Button from '../../components/Button';
-import Header from '../../components/Header';
-import UserInfo from '../../components/UserInfo';
-import { User, Credentials, ApiCredentials } from 'react-native-auth0';
-import type { ClassDemoStackParamList } from '../../navigation/ClassDemoNavigator';
-import LabeledInput from '../../components/LabeledInput';
-import Result from '../../components/Result';
-
-type ProfileRouteProp = RouteProp;
-
-type Props = {
- route: ProfileRouteProp;
- navigation: NavigationProp;
-};
-
-interface State {
- user: User | null;
- result: Credentials | ApiCredentials | object | boolean | null;
- error: Error | null;
- audience: string;
- webAppUrl: string;
-}
-
-class ClassProfileScreen extends Component {
- constructor(props: Props) {
- super(props);
- const user = this.decodeIdToken(props.route.params.credentials.idToken);
- this.state = {
- user,
- result: null,
- error: null,
- audience: '',
- webAppUrl: 'https://your-web-app.com/login',
- };
- }
-
- decodeIdToken = (idToken: string): User | null => {
- try {
- return jwtDecode(idToken);
- } catch {
- return null;
- }
- };
-
- runTest = async (testFn: () => Promise, title: string) => {
- this.setState({ error: null, result: null });
- try {
- const res = await testFn();
- this.setState({ result: res ?? { success: `${title} completed` } });
- } catch (e) {
- this.setState({ error: e as Error });
- }
- };
-
- onLogout = async () => {
- try {
- await auth0.webAuth.clearSession();
- await auth0.credentialsManager.clearCredentials();
- this.props.navigation.goBack();
- } catch (e) {
- Alert.alert('Error', (e as Error).message);
- }
- };
-
- render() {
- const { user, result, error, audience, webAppUrl } = this.state;
- const { accessToken, idToken } = this.props.route.params.credentials;
-
- return (
-
-
-
-
-
-
-
-
- this.runTest(
- () => auth0.credentialsManager.getCredentials(),
- 'Get Credentials'
- )
- }
- title="credentialsManager.getCredentials()"
- />
-
- this.runTest(
- () => auth0.credentialsManager.hasValidCredentials(),
- 'Check Valid Credentials'
- )
- }
- title="credentialsManager.hasValidCredentials()"
- />
-
- this.runTest(
- () => auth0.credentialsManager.clearCredentials(),
- 'Clear Credentials'
- )
- }
- title="credentialsManager.clearCredentials()"
- style={styles.destructiveButton}
- />
-
-
-
- this.setState({ audience: text })}
- autoCapitalize="none"
- />
-
- this.runTest(
- () => auth0.credentialsManager.getApiCredentials(audience),
- 'Get API Credentials'
- )
- }
- title="credentialsManager.getApiCredentials()"
- />
-
- this.runTest(
- () => auth0.credentialsManager.clearApiCredentials(audience),
- 'Clear API Credentials'
- )
- }
- title="credentialsManager.clearApiCredentials()"
- style={styles.secondaryButton}
- />
-
-
-
-
- Exchange your refresh token for a Session Transfer Token to enable
- seamless SSO to your web application.
-
- this.setState({ webAppUrl: text })}
- autoCapitalize="none"
- placeholder="https://your-web-app.com/login"
- />
-
- this.runTest(
- () => auth0.credentialsManager.getSSOCredentials(),
- 'Get SSO Credentials'
- )
- }
- title="credentialsManager.getSSOCredentials()"
- />
- {
- try {
- this.setState({ error: null });
- const ssoCredentials =
- await auth0.credentialsManager.getSSOCredentials();
- this.setState({ result: ssoCredentials });
-
- // Open web app with session transfer token
- const url = `${webAppUrl}?session_transfer_token=${ssoCredentials.sessionTransferToken}`;
-
- Alert.alert(
- 'Open Web App',
- `Open ${webAppUrl} with session transfer token?`,
- [
- { text: 'Cancel', style: 'cancel' },
- {
- text: 'Open',
- onPress: async () => {
- const supported = await Linking.canOpenURL(url);
- if (supported) {
- await Linking.openURL(url);
- } else {
- Alert.alert('Error', `Cannot open URL: ${url}`);
- }
- },
- },
- ]
- );
- } catch (e) {
- this.setState({ error: e as Error });
- }
- }}
- title="Get SSO Credentials & Open Web App"
- style={styles.primaryButton}
- />
-
-
-
-
- this.props.navigation.navigate('ClassApiTests', {
- accessToken,
- idToken,
- })
- }
- title="Go to API Tests"
- />
-
-
-
-
- );
- }
-}
-
-const Section = ({
- title,
- children,
-}: {
- title: string;
- children: React.ReactNode;
-}) => (
-
- {title}
- {children}
-
-);
-
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#FFFFFF' },
- content: { padding: 16, paddingBottom: 50, alignItems: 'center' },
- section: {
- width: '100%',
- marginBottom: 20,
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- padding: 16,
- },
- sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 12 },
- buttonGroup: { gap: 10 },
- description: { fontSize: 14, color: '#757575', marginBottom: 10 },
- destructiveButton: { backgroundColor: '#424242' },
- secondaryButton: { backgroundColor: '#FF9800' },
- primaryButton: { backgroundColor: '#4CAF50' },
-});
-
-export default ClassProfileScreen;
diff --git a/example/src/screens/hooks/CredentialsScreen.tsx b/example/src/screens/hooks/CredentialsScreen.tsx
deleted file mode 100644
index c9927192e..000000000
--- a/example/src/screens/hooks/CredentialsScreen.tsx
+++ /dev/null
@@ -1,246 +0,0 @@
-import React, { useState } from 'react';
-import {
- ScrollView,
- StyleSheet,
- View,
- Text,
- Linking,
- Alert,
-} from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import {
- useAuth0,
- Credentials,
- ApiCredentials,
- CredentialsManagerError,
- CredentialsManagerErrorCodes,
-} from 'react-native-auth0';
-import Button from '../../components/Button';
-import Header from '../../components/Header';
-import Result from '../../components/Result';
-import LabeledInput from '../../components/LabeledInput';
-
-const CredentialsScreen = () => {
- const {
- getCredentials,
- hasValidCredentials,
- clearCredentials,
- getApiCredentials,
- clearApiCredentials,
- revokeRefreshToken,
- getSSOCredentials,
- } = useAuth0();
-
- const [result, setResult] = useState<
- Credentials | ApiCredentials | object | boolean | null
- >(null);
- const [error, setError] = useState(null);
- const [audience, setAudience] = useState('');
- const [scope, setScope] = useState('openid profile email');
- const [webAppUrl, setWebAppUrl] = useState('https://your-web-app.com/login');
-
- const runTest = async (testFn: () => Promise, title: string) => {
- setError(null);
- setResult(null);
- try {
- const res = await testFn();
- setResult(res ?? { success: `${title} completed` });
- } catch (e) {
- setError(e as Error);
- // Demonstrate usage of CredentialsManagerErrorCodes for type-safe error handling
- if (e instanceof CredentialsManagerError) {
- const credError: CredentialsManagerError = e;
- switch (credError.type) {
- case CredentialsManagerErrorCodes.NO_CREDENTIALS:
- Alert.alert(
- 'No Credentials',
- 'No credentials are stored. Please log in first.'
- );
- break;
- case CredentialsManagerErrorCodes.NO_REFRESH_TOKEN:
- Alert.alert(
- 'No Refresh Token',
- 'Refresh token is not available. Make sure to request the "offline_access" scope during login.'
- );
- break;
- default:
- console.log(
- `Credentials error: ${credError.type} - ${credError.message}`
- );
- }
- }
- }
- };
-
- return (
-
-
-
-
-
-
- runTest(getCredentials, 'Get Credentials')}
- title="getCredentials()"
- />
-
- runTest(hasValidCredentials, 'Check Valid Credentials')
- }
- title="hasValidCredentials()"
- />
- {
- if (
- typeof result === 'object' &&
- result &&
- 'refreshToken' in result
- ) {
- const token = (result as Credentials).refreshToken;
- if (token) {
- runTest(
- () => revokeRefreshToken({ refreshToken: token }),
- 'Revoke Refresh Token'
- );
- }
- }
- }}
- title="revokeRefreshToken()"
- disabled={
- !(
- typeof result === 'object' &&
- result &&
- 'refreshToken' in result
- )
- }
- />
- runTest(clearCredentials, 'Clear Credentials')}
- title="clearCredentials()"
- style={styles.destructiveButton}
- />
-
-
-
-
-
-
- runTest(
- () => getApiCredentials(audience, scope),
- 'Get API Credentials'
- )
- }
- title="getApiCredentials()"
- />
-
- runTest(
- () => clearApiCredentials(audience),
- 'Clear API Credentials'
- )
- }
- title="clearApiCredentials()"
- style={styles.secondaryButton}
- />
-
-
-
-
- Exchange your refresh token for a Session Transfer Token to enable
- seamless SSO to your web application.
-
-
- runTest(getSSOCredentials, 'Get SSO Credentials')}
- title="getSSOCredentials()"
- />
- {
- try {
- setError(null);
- const ssoCredentials = await getSSOCredentials();
- setResult(ssoCredentials);
-
- // Open web app with session transfer token
- const url = `${webAppUrl}?session_transfer_token=${ssoCredentials.sessionTransferToken}`;
-
- Alert.alert(
- 'Open Web App',
- `Open ${webAppUrl} with session transfer token?`,
- [
- { text: 'Cancel', style: 'cancel' },
- {
- text: 'Open',
- onPress: async () => {
- const supported = await Linking.canOpenURL(url);
- if (supported) {
- await Linking.openURL(url);
- } else {
- Alert.alert('Error', `Cannot open URL: ${url}`);
- }
- },
- },
- ]
- );
- } catch (e) {
- setError(e as Error);
- }
- }}
- title="Get SSO Credentials & Open Web App"
- style={styles.primaryButton}
- />
-
-
-
- );
-};
-
-const Section = ({
- title,
- children,
-}: {
- title: string;
- children: React.ReactNode;
-}) => (
-
- {title}
- {children}
-
-);
-
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#FFFFFF' },
- content: { padding: 16, paddingBottom: 50 },
- section: {
- marginBottom: 20,
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- padding: 16,
- },
- sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 12 },
- buttonGroup: { gap: 10 },
- description: { fontSize: 14, color: '#757575', marginBottom: 10 },
- destructiveButton: { backgroundColor: '#424242' },
- secondaryButton: { backgroundColor: '#FF9800' },
- primaryButton: { backgroundColor: '#4CAF50' },
-});
-
-export default CredentialsScreen;
diff --git a/example/src/screens/hooks/Home.tsx b/example/src/screens/hooks/Home.tsx
deleted file mode 100644
index 73fe7bcce..000000000
--- a/example/src/screens/hooks/Home.tsx
+++ /dev/null
@@ -1,1222 +0,0 @@
-import React, { useState } from 'react';
-import {
- ScrollView,
- View,
- Text,
- StyleSheet,
- Alert,
- TouchableOpacity,
- Image,
- Linking,
- Platform,
-} from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import {
- useAuth0,
- WebAuthError,
- WebAuthErrorCodes,
- MfaError,
- MfaErrorCodes,
- MfaFactorType,
- PasskeyError,
- PasskeyErrorCodes,
-} from 'react-native-auth0';
-import type {
- PasswordlessChallenge,
- MfaAuthenticator,
- MfaEnrollmentChallenge,
- MfaChallengeResult,
-} from 'react-native-auth0';
-import Button from '../../components/Button';
-import Header from '../../components/Header';
-import LabeledInput from '../../components/LabeledInput';
-import Result from '../../components/Result';
-import config from '../../auth0-configuration';
-import {
- createPasskey,
- getPasskey,
- PasskeyModuleErrorCodes,
-} from '../../passkey/PasskeyModule';
-
-type MfaStep =
- | 'idle'
- | 'list'
- | 'enroll-select'
- | 'enroll-details'
- | 'challenge'
- | 'verify'
- | 'complete';
-
-type EnrollType = MfaFactorType;
-
-const HomeScreen = () => {
- const {
- authorize,
- resumeSession,
- loginWithPasswordRealm,
- sendEmailCode,
- authorizeWithEmail,
- mfa,
- passkeySignupChallenge,
- passkeyLoginChallenge,
- getTokenByPasskey,
- passwordless,
- error,
- } = useAuth0();
-
- const [email, setEmail] = useState('');
- const [password, setPassword] = useState('');
- const [otp, setOtp] = useState('');
- const [showOtpInput, setShowOtpInput] = useState(false);
- const [apiError, setApiError] = useState(null);
- const [result, setResult] = useState(null);
- const [passkeyEmail, setPasskeyEmail] = useState('');
- const [loading, setLoading] = useState(false);
- const [lastResult, setLastResult] = useState(null);
- const [otpMethod, setOtpMethod] = useState<'email' | 'phone'>('email');
- const [otpEmail, setOtpEmail] = useState('');
- const [otpPhone, setOtpPhone] = useState('');
- const [otpCode, setOtpCode] = useState('');
- const [otpChallenge, setOtpChallenge] =
- useState(null);
- const [useTrustedWebActivity, setUseTrustedWebActivity] = useState(false);
- const [ephemeralSession, setEphemeralSession] = useState(false);
-
- // MFA wizard state
- const [mfaToken, setMfaToken] = useState('');
- const [mfaStep, setMfaStep] = useState('idle');
- const [mfaLoading, setMfaLoading] = useState(false);
- const [authenticators, setAuthenticators] = useState([]);
- const [selectedAuthenticator, setSelectedAuthenticator] =
- useState(null);
- const [enrollType, setEnrollType] = useState(null);
- const [enrollPhoneNumber, setEnrollPhoneNumber] = useState('');
- const [enrollEmail, setEnrollEmail] = useState('');
- const [enrollmentChallenge, setEnrollmentChallenge] =
- useState(null);
- const [challengeResult, setChallengeResult] =
- useState(null);
- const [verifyCode, setVerifyCode] = useState('');
- const [verifyBindingCode, setVerifyBindingCode] = useState('');
- const [verifyScope, setVerifyScope] = useState('');
- const [verifyAudience, setVerifyAudience] = useState('');
-
- const clearResult = () => {
- setResult(null);
- setApiError(null);
- };
-
- const resetMfaWizard = () => {
- setMfaStep('idle');
- setAuthenticators([]);
- setSelectedAuthenticator(null);
- setEnrollType(null);
- setEnrollPhoneNumber('');
- setEnrollEmail('');
- setEnrollmentChallenge(null);
- setChallengeResult(null);
- setVerifyCode('');
- setVerifyBindingCode('');
- setVerifyScope('');
- setVerifyAudience('');
- setMfaLoading(false);
- clearResult();
- };
-
- const onLogin = async () => {
- try {
- await authorize(
- {
- scope: 'openid profile email offline_access',
- audience: `https://${config.domain}/api/v2/`,
- },
- { useTrustedWebActivity, ephemeralSession }
- );
- } catch (e: any) {
- if (e instanceof WebAuthError) {
- switch (e.type) {
- case WebAuthErrorCodes.USER_CANCELLED:
- Alert.alert('Login Cancelled', 'You cancelled the login process.');
- break;
- case WebAuthErrorCodes.TIMEOUT_ERROR:
- Alert.alert('Login Timeout', 'The login process timed out.');
- break;
- default:
- Alert.alert('Authentication Error', e.message);
- }
- } else {
- Alert.alert('Error', 'An unexpected error occurred during login.');
- }
- }
- };
-
- const onResumeSession = async () => {
- try {
- const credentials = await resumeSession();
- if (credentials) {
- Alert.alert('Recovered', 'Login was recovered after process death.');
- } else {
- Alert.alert('Nothing to recover', 'No pending login was found.');
- }
- } catch (e) {
- setApiError(e as Error);
- }
- };
-
- const onLoginWithPassword = async () => {
- clearResult();
- try {
- await loginWithPasswordRealm({
- username: email,
- password: password,
- realm: 'Username-Password-Authentication',
- });
- } catch (e: any) {
- if (e?.json?.mfa_token) {
- const token = e.json.mfa_token;
- setMfaToken(token);
- Alert.alert(
- 'MFA Required',
- 'Multi-factor authentication is required. Checking enrolled authenticators...'
- );
- await startMfaFlow(token);
- } else {
- setApiError(e as Error);
- }
- }
- };
-
- const startMfaFlow = async (token: string) => {
- setMfaLoading(true);
- clearResult();
- try {
- const list = await mfa.getAuthenticators({
- mfaToken: token,
- factorsAllowed: [
- MfaFactorType.OTP,
- MfaFactorType.SMS,
- MfaFactorType.EMAIL,
- MfaFactorType.PUSH,
- MfaFactorType.VOICE,
- ],
- });
- setAuthenticators(list);
-
- const activeAuthenticator = list.find((auth) => auth.active);
- if (activeAuthenticator) {
- setSelectedAuthenticator(activeAuthenticator);
- setMfaStep('challenge');
- await triggerChallenge(token, activeAuthenticator);
- } else {
- setMfaStep('enroll-select');
- }
- } catch (e) {
- handleMfaError(e, 'Failed to list authenticators.');
- setMfaStep('idle');
- } finally {
- setMfaLoading(false);
- }
- };
-
- const triggerChallenge = async (token: string, auth: MfaAuthenticator) => {
- setMfaLoading(true);
- try {
- const res = await mfa.challenge({
- mfaToken: token,
- authenticatorId: auth.id,
- });
- setChallengeResult(res);
- setMfaStep('verify');
- } catch (e) {
- handleMfaError(e, 'Challenge failed.');
- setMfaStep('list');
- } finally {
- setMfaLoading(false);
- }
- };
-
- const onSendEmailCode = async () => {
- try {
- await sendEmailCode({ email });
- setShowOtpInput(true);
- Alert.alert('Success', 'Check your email for the one-time code.');
- } catch (e) {
- setApiError(e as Error);
- }
- };
-
- const onLoginWithEmailCode = async () => {
- try {
- await authorizeWithEmail({ email, code: otp });
- } catch (e) {
- setApiError(e as Error);
- }
- };
-
- // --- MFA Wizard Handlers ---
-
- const handleMfaError = (e: unknown, fallbackMsg: string) => {
- if (e instanceof MfaError) {
- if (
- e.type === MfaErrorCodes.EXPIRED_MFA_TOKEN ||
- e.type === MfaErrorCodes.INVALID_MFA_TOKEN
- ) {
- Alert.alert('Session Expired', 'Please log in again.');
- setMfaToken('');
- resetMfaWizard();
- return;
- }
- Alert.alert('MFA Error', e.message);
- setApiError(e);
- } else {
- setApiError(e as Error);
- Alert.alert('Error', fallbackMsg);
- }
- };
-
- const onStartMfa = async () => {
- await startMfaFlow(mfaToken);
- };
-
- const onSelectAuthenticator = (auth: MfaAuthenticator) => {
- setSelectedAuthenticator(auth);
- setChallengeResult(null);
- setMfaStep('challenge');
- triggerChallenge(mfaToken, auth);
- };
-
- const onSelectEnrollType = (type: EnrollType) => {
- setEnrollType(type);
- if (type === MfaFactorType.OTP || type === MfaFactorType.PUSH) {
- onEnroll(type);
- } else {
- setMfaStep('enroll-details');
- }
- };
-
- const onEnroll = async (type?: EnrollType) => {
- const factor = type || enrollType;
- if (!factor) return;
-
- setMfaLoading(true);
- try {
- let challenge: MfaEnrollmentChallenge;
- if (factor === MfaFactorType.SMS) {
- challenge = await mfa.enroll({
- mfaToken,
- factorType: MfaFactorType.SMS,
- phoneNumber: enrollPhoneNumber,
- });
- } else if (factor === MfaFactorType.VOICE) {
- challenge = await mfa.enroll({
- mfaToken,
- factorType: MfaFactorType.VOICE,
- phoneNumber: enrollPhoneNumber,
- });
- } else if (factor === MfaFactorType.EMAIL) {
- challenge = await mfa.enroll({
- mfaToken,
- factorType: MfaFactorType.EMAIL,
- email: enrollEmail,
- });
- } else {
- challenge = await mfa.enroll({ mfaToken, factorType: factor });
- }
- setEnrollmentChallenge(challenge);
- setMfaStep('verify');
- } catch (e) {
- handleMfaError(e, 'Enrollment failed.');
- } finally {
- setMfaLoading(false);
- }
- };
-
- const onVerify = async () => {
- setMfaLoading(true);
- try {
- let credentials;
- // scope/audience are optional: supply them to mint an access token for a
- // specific API once MFA succeeds.
- const extra = {
- scope: verifyScope || undefined,
- audience: verifyAudience || undefined,
- };
- const oobCode =
- challengeResult?.oobCode ||
- (enrollmentChallenge?.type === 'oob' ||
- enrollmentChallenge?.type === 'push'
- ? enrollmentChallenge.oobCode
- : undefined);
-
- if (oobCode) {
- credentials = await mfa.verify({
- mfaToken,
- oobCode,
- bindingCode: verifyBindingCode || undefined,
- ...extra,
- });
- } else if (enrollmentChallenge?.type === 'recovery-code') {
- credentials = await mfa.verify({
- mfaToken,
- recoveryCode: verifyCode,
- ...extra,
- });
- } else {
- credentials = await mfa.verify({ mfaToken, otp: verifyCode, ...extra });
- }
- setResult({
- success: true,
- accessToken: credentials.accessToken.substring(0, 20) + '...',
- });
- setMfaStep('complete');
- } catch (e) {
- handleMfaError(e, 'Verification failed.');
- } finally {
- setMfaLoading(false);
- }
- };
-
- // --- MFA Wizard UI ---
-
- const renderMfaWizard = () => {
- switch (mfaStep) {
- case 'idle':
- return (
- <>
-
- Get an mfa_token from a password login with MFA enabled, or paste
- one manually.
-
-
-
- >
- );
-
- case 'list':
- return (
- <>
- Step 1: Select Authenticator
- {authenticators.length > 0 ? (
- <>
-
- Select an enrolled authenticator to challenge:
-
- {authenticators.map((auth) => (
- onSelectAuthenticator(auth)}
- >
-
- {auth.type ?? auth.authenticatorType}
- {auth.oobChannel ? ` (${auth.oobChannel})` : ''}
-
-
- {auth.id} · authenticatorType: {auth.authenticatorType}
- {auth.active ? '' : ' · inactive'}
-
-
- ))}
-
- >
- ) : (
-
- No authenticators enrolled. Enroll a new one below.
-
- )}
- setMfaStep('enroll-select')}
- title="Enroll New Authenticator"
- />
-
- >
- );
-
- case 'enroll-select':
- return (
- <>
- Step 2: Choose Factor Type
- onSelectEnrollType(MfaFactorType.OTP)}
- title="TOTP (Authenticator App)"
- disabled={mfaLoading}
- />
- onSelectEnrollType(MfaFactorType.SMS)}
- title="SMS"
- disabled={mfaLoading}
- />
- onSelectEnrollType(MfaFactorType.VOICE)}
- title="Voice"
- disabled={mfaLoading}
- />
-
- Voice is a distinct channel on web only. On native (iOS/Android)
- it falls back to SMS on the same number.
-
- onSelectEnrollType(MfaFactorType.EMAIL)}
- title="Email"
- disabled={mfaLoading}
- />
- onSelectEnrollType(MfaFactorType.PUSH)}
- title="Push Notification"
- disabled={mfaLoading}
- />
- setMfaStep('list')} title="Back" />
- >
- );
-
- case 'enroll-details':
- return (
- <>
- Step 2: Enter Details
- {(enrollType === MfaFactorType.SMS ||
- enrollType === MfaFactorType.VOICE) && (
- <>
-
- onEnroll()}
- title={
- enrollType === MfaFactorType.VOICE
- ? 'Enroll Voice'
- : 'Enroll SMS'
- }
- disabled={!enrollPhoneNumber || mfaLoading}
- />
- >
- )}
- {enrollType === MfaFactorType.EMAIL && (
- <>
-
- onEnroll()}
- title="Enroll Email"
- disabled={!enrollEmail || mfaLoading}
- />
- >
- )}
- setMfaStep('enroll-select')} title="Back" />
- >
- );
-
- case 'verify':
- return (
- <>
- Step 3: Verify
- {enrollmentChallenge?.type === 'totp' && (
-
- {enrollmentChallenge.barcodeUri && (
- <>
-
-
-
-
- Linking.openURL(enrollmentChallenge.barcodeUri!)
- }
- title="Open in Authenticator App"
- />
- >
- )}
- Secret:
-
- {enrollmentChallenge.secret}
-
-
- )}
- {enrollmentChallenge?.type === 'push' && (
-
-
- Scan this QR code with the Auth0 Guardian app to pair, then
- approve the push notification on your device.
-
- {enrollmentChallenge.barcodeUri ? (
-
-
-
- ) : null}
-
-
- )}
- {challengeResult && (
-
-
- Challenge Type: {challengeResult.challengeType}
-
- {challengeResult.bindingMethod && (
-
- Binding Method: {challengeResult.bindingMethod}
-
- )}
-
- )}
- {(challengeResult?.challengeType === 'oob' ||
- enrollmentChallenge?.type === 'oob') && (
- <>
-
- A code has been sent to your device. Enter the binding code
- below.
-
-
-
- >
- )}
- {(challengeResult?.challengeType === 'otp' ||
- enrollmentChallenge?.type === 'totp') && (
- <>
-
-
- >
- )}
- {enrollmentChallenge?.type === 'recovery-code' && (
-
-
- Save this recovery code somewhere safe — it is shown only
- once. Enter it below to complete verification.
-
- Recovery Code:
-
- {enrollmentChallenge.recoveryCode}
-
-
-
-
- )}
-
-
- Optional: request a scope/audience to mint an API access token on
- successful verification.
-
-
-
- setMfaStep('list')} title="Back" />
- >
- );
-
- case 'complete':
- return (
- <>
- MFA Complete
- Authentication successful!
- {result && (
-
- )}
-
- >
- );
-
- default:
- return null;
- }
- };
-
- // --- Passwordless OTP (Database Connection) ---
-
- const onSendOtpChallenge = async () => {
- setApiError(null);
- setLoading(true);
- try {
- const challenge =
- otpMethod === 'email'
- ? await passwordless.challengeWithEmail({
- email: otpEmail,
- connection: 'Username-Password-Authentication',
- allowSignup: true,
- })
- : await passwordless.challengeWithPhoneNumber({
- phoneNumber: otpPhone,
- connection: 'Username-Password-Authentication',
- deliveryMethod: 'text',
- allowSignup: true,
- });
- setOtpChallenge(challenge);
- Alert.alert(
- 'Success',
- otpMethod === 'email'
- ? 'Check your email for the one-time code.'
- : 'Check your phone for the one-time code.'
- );
- } catch (e) {
- setApiError(e as Error);
- } finally {
- setLoading(false);
- }
- };
-
- const onLoginWithOtp = async () => {
- if (!otpChallenge) {
- return;
- }
- setApiError(null);
- setLoading(true);
- try {
- // A successful loginWithOTP updates the hook's auth state, which
- // navigates the app to the authenticated stack automatically.
- await passwordless.loginWithOTP({
- challenge: otpChallenge,
- otp: otpCode,
- });
- } catch (e) {
- setApiError(e as Error);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Passkey Handlers ---
-
- const handlePasskeyError = (e: any) => {
- if (e?.code === PasskeyModuleErrorCodes.USER_CANCELLED) {
- Alert.alert('Cancelled', 'You dismissed the passkey prompt.');
- setApiError(e as Error);
- return;
- }
- if (e instanceof PasskeyError) {
- switch (e.type) {
- case PasskeyErrorCodes.NOT_AVAILABLE:
- Alert.alert(
- 'Not Available',
- 'Passkeys are not supported on this device.'
- );
- break;
- case PasskeyErrorCodes.CHALLENGE_FAILED:
- Alert.alert('Challenge Failed', e.message);
- break;
- case PasskeyErrorCodes.EXCHANGE_FAILED:
- Alert.alert('Exchange Failed', e.message);
- break;
- default:
- Alert.alert('Passkey Error', `[${e.type}] ${e.message}`);
- }
- }
- setApiError(e as Error);
- };
-
- // --- Full-flow passkey handlers ---
-
- const onPasskeySignup = async () => {
- setApiError(null);
- setLastResult(null);
- setLoading(true);
- try {
- const challenge = await passkeySignupChallenge({
- email: passkeyEmail || undefined,
- realm: 'Username-Password-Authentication',
- });
-
- const credentialJson = await createPasskey(challenge.authParamsPublicKey);
-
- const credentials = await getTokenByPasskey({
- authSession: challenge.authSession,
- authResponse: credentialJson,
- realm: 'Username-Password-Authentication',
- });
-
- setLastResult({
- step: 'signup-complete',
- accessToken: `${credentials.accessToken.substring(0, 30)}...`,
- tokenType: credentials.tokenType,
- });
- Alert.alert('Success', 'Passkey signup complete!');
- } catch (e) {
- handlePasskeyError(e);
- } finally {
- setLoading(false);
- }
- };
-
- const onPasskeyLogin = async () => {
- setApiError(null);
- setLastResult(null);
- setLoading(true);
- try {
- const challenge = await passkeyLoginChallenge({
- realm: 'Username-Password-Authentication',
- });
-
- const credentialJson = await getPasskey(challenge.authParamsPublicKey);
-
- const credentials = await getTokenByPasskey({
- authSession: challenge.authSession,
- authResponse: credentialJson,
- realm: 'Username-Password-Authentication',
- });
-
- setLastResult({
- step: 'login-complete',
- accessToken: `${credentials.accessToken.substring(0, 30)}...`,
- tokenType: credentials.tokenType,
- });
- Alert.alert('Success', 'Passkey login complete!');
- } catch (e) {
- handlePasskeyError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Step-by-step handlers for testing individual methods ---
-
- const onTestChallenge = async (type: 'signup' | 'login') => {
- setApiError(null);
- setLastResult(null);
- setLoading(true);
- try {
- const challenge =
- type === 'signup'
- ? await passkeySignupChallenge({
- email: passkeyEmail || undefined,
- realm: 'Username-Password-Authentication',
- })
- : await passkeyLoginChallenge({
- realm: 'Username-Password-Authentication',
- });
-
- setLastResult({
- step: `${type}Challenge`,
- authSession: challenge.authSession,
- authParamsPublicKey: challenge.authParamsPublicKey,
- });
- console.log(`${type} challenge:`, JSON.stringify(challenge, null, 2));
- } catch (e) {
- handlePasskeyError(e);
- } finally {
- setLoading(false);
- }
- };
-
- const onTestExchange = async () => {
- const result = lastResult as any;
- if (!result?.authSession || !result?.authParamsPublicKey) {
- Alert.alert(
- 'Error',
- 'Run a challenge first (Signup Challenge or Login Challenge).'
- );
- return;
- }
- setApiError(null);
- setLoading(true);
- try {
- const isSignup = result.step === 'signupChallenge';
- const credentialJson = isSignup
- ? await createPasskey(result.authParamsPublicKey)
- : await getPasskey(result.authParamsPublicKey);
-
- const credentials = await getTokenByPasskey({
- authSession: result.authSession,
- authResponse: credentialJson,
- realm: 'Username-Password-Authentication',
- });
-
- setLastResult({
- step: 'exchange',
- accessToken: `${credentials.accessToken.substring(0, 30)}...`,
- tokenType: credentials.tokenType,
- });
- Alert.alert('Success', 'Token exchange complete!');
- } catch (e) {
- handlePasskeyError(e);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
-
- React Native Auth0 Hooks
-
- {error && }
- {apiError && (
-
- )}
-
-
-
- <>
-
- Trusted Web Activity opens login full-screen (no URL bar).
- Requires registering the app's SHA-256 Key Hash in the Auth0
- Dashboard; otherwise it falls back to a Custom Tab. Android only.
-
- {
- setUseTrustedWebActivity((prev) => !prev);
- setEphemeralSession(false);
- }}
- title={`Trusted Web Activity: ${
- useTrustedWebActivity ? 'On' : 'Off'
- }`}
- style={!useTrustedWebActivity ? styles.inactiveButton : undefined}
- />
-
- Ephemeral session disables SSO by running login in an
- incognito-like browser session. On Android this needs Chrome 136+
- and falls back to a regular Custom Tab otherwise.
-
- {
- setEphemeralSession((prev) => !prev);
- setUseTrustedWebActivity(false);
- }}
- title={`Ephemeral Session: ${ephemeralSession ? 'On' : 'Off'}`}
- style={!ephemeralSession ? styles.inactiveButton : undefined}
- />
-
- Recovers a login that completed after the OS killed the app
- process. No-op on iOS/web.
-
-
- >
-
-
-
-
-
-
-
- If MFA is enabled, a failed login will return an mfa_token and
- automatically start the MFA wizard.
-
-
-
-
-
-
- {showOtpInput && (
- <>
-
-
- >
- )}
-
-
-
-
- {Platform.OS !== 'web' && (
-
-
- Two-step flow on a database connection with email_otp / phone_otp
- enabled: challenge → verify code.
-
-
-
- setOtpMethod('email')}
- title="Email"
- style={[
- styles.halfButton,
- otpMethod !== 'email' && styles.inactiveButton,
- ]}
- />
- setOtpMethod('phone')}
- title="Phone"
- style={[
- styles.halfButton,
- otpMethod !== 'phone' && styles.inactiveButton,
- ]}
- />
-
-
- {otpMethod === 'email' ? (
-
- ) : (
-
- )}
-
-
- {otpChallenge && (
- <>
-
-
- >
- )}
-
- )}
-
- {Platform.OS !== 'web' && (
-
-
- Full passkey flow: challenge → credential manager → exchange.
-
-
-
-
-
-
-
-
-
-
- Or test individual steps:
-
-
-
- onTestChallenge('signup')}
- title="Signup Challenge"
- loading={loading}
- style={styles.halfButton}
- />
- onTestChallenge('login')}
- title="Login Challenge"
- loading={loading}
- style={styles.halfButton}
- />
-
-
-
-
- {lastResult && (
-
- Last Result:
-
- {JSON.stringify(
- lastResult,
- (key, val) => (key.startsWith('_') ? undefined : val),
- 2
- )}
-
-
- )}
-
- )}
-
-
- );
-};
-
-const Section = ({
- title,
- children,
-}: {
- title: string;
- children: React.ReactNode;
-}) => (
-
- {title}
- {children}
-
-);
-
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#FFFFFF' },
- content: { padding: 16, gap: 20, paddingBottom: 50 },
- title: {
- fontSize: 24,
- fontWeight: 'bold',
- marginBottom: 20,
- textAlign: 'center',
- },
- section: {
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- padding: 16,
- gap: 10,
- },
- sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 8 },
- stepTitle: {
- fontSize: 16,
- fontWeight: '700',
- color: '#333',
- marginBottom: 4,
- },
- hint: { fontSize: 12, color: '#888', fontStyle: 'italic' },
- authItem: {
- borderWidth: 1,
- borderColor: '#CCC',
- borderRadius: 6,
- padding: 12,
- backgroundColor: '#F9F9F9',
- },
- authItemTitle: { fontSize: 14, fontWeight: '600' },
- authItemSubtitle: { fontSize: 11, color: '#666', marginTop: 2 },
- divider: {
- height: 1,
- backgroundColor: '#E0E0E0',
- marginVertical: 8,
- },
- infoBox: {
- backgroundColor: '#F0F4FF',
- borderRadius: 6,
- padding: 10,
- gap: 4,
- },
- description: { fontSize: 13, color: '#666', marginBottom: 4 },
- row: { flexDirection: 'row', gap: 8 },
- halfButton: { flex: 1, minWidth: 0 },
- inactiveButton: { backgroundColor: '#BDBDBD' },
- resultBox: {
- backgroundColor: '#F5F5F5',
- borderRadius: 6,
- padding: 10,
- gap: 4,
- },
- infoLabel: { fontSize: 12, fontWeight: '600', color: '#444' },
- infoValue: { fontSize: 12, color: '#333', fontFamily: 'monospace' },
- qrContainer: { alignItems: 'center', marginVertical: 12 },
- qrImage: { width: 200, height: 200 },
- successText: {
- fontSize: 16,
- fontWeight: '600',
- color: '#2E7D32',
- textAlign: 'center',
- },
- resultLabel: { fontSize: 12, fontWeight: '600', color: '#333' },
- resultValue: { fontSize: 11, color: '#555', fontFamily: 'monospace' },
-});
-
-export default HomeScreen;
diff --git a/example/src/screens/hooks/More.tsx b/example/src/screens/hooks/More.tsx
deleted file mode 100644
index a3905a066..000000000
--- a/example/src/screens/hooks/More.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import React, { useState } from 'react';
-import { ScrollView, StyleSheet, View, Text } from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import { useAuth0 } from 'react-native-auth0';
-import Button from '../../components/Button';
-import Header from '../../components/Header';
-import Result from '../../components/Result';
-import LabeledInput from '../../components/LabeledInput';
-
-const MoreScreen = () => {
- const { createUser, resetPassword, authorizeWithExchangeNativeSocial } =
- useAuth0();
-
- const [apiResult, setApiResult] = useState(null);
- const [apiError, setApiError] = useState(null);
-
- const [newUserEmail, setNewUserEmail] = useState('');
- const [newUserPassword, setNewUserPassword] = useState('');
- const [resetEmail, setResetEmail] = useState('');
-
- const runTest = async (testFn: () => Promise, title: string) => {
- setApiError(null);
- setApiResult(null);
- try {
- const res = await testFn();
- setApiResult(res ?? { success: `${title} completed` });
- } catch (e) {
- setApiError(e as Error);
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
- runTest(
- () =>
- createUser({
- email: newUserEmail,
- password: newUserPassword,
- connection: 'Username-Password-Authentication',
- }),
- 'Create User'
- )
- }
- title="hooks.createUser()"
- />
-
-
-
-
-
- runTest(
- () =>
- resetPassword({
- email: resetEmail,
- connection: 'Username-Password-Authentication',
- }),
- 'Reset Password'
- )
- }
- title="hooks.resetPassword()"
- />
-
-
-
-
- This requires getting a token from a native social SDK (e.g., Google
- Sign-In) first.
-
-
- runTest(
- () =>
- authorizeWithExchangeNativeSocial({
- subjectToken: 'NATIVE_SOCIAL_TOKEN',
- subjectTokenType:
- 'http://auth0.com/oauth/token-type/google-access-token',
- }),
- 'Exchange Social Token'
- )
- }
- title="hooks.authorizeWithExchangeNativeSocial()"
- />
-
-
-
- );
-};
-
-const Section = ({
- title,
- children,
-}: {
- title: string;
- children: React.ReactNode;
-}) => (
-
- {title}
- {children}
-
-);
-
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#FFFFFF' },
- content: { padding: 16, paddingBottom: 50 },
- section: {
- marginBottom: 20,
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- padding: 16,
- gap: 10,
- },
- sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 8 },
- description: { fontSize: 14, color: '#757575', marginBottom: 10 },
-});
-
-export default MoreScreen;
diff --git a/example/src/screens/hooks/MyAccountScreen.tsx b/example/src/screens/hooks/MyAccountScreen.tsx
deleted file mode 100644
index c94d6cb24..000000000
--- a/example/src/screens/hooks/MyAccountScreen.tsx
+++ /dev/null
@@ -1,634 +0,0 @@
-import React, { useState } from 'react';
-import {
- ScrollView,
- View,
- Text,
- StyleSheet,
- Alert,
- Platform,
- TextInput,
-} from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import {
- useAuth0,
- MyAccountError,
- PasskeyError,
- PasskeyErrorCodes,
- PreferredAuthenticationMethods,
-} from 'react-native-auth0';
-import Button from '../../components/Button';
-import Header from '../../components/Header';
-import Result from '../../components/Result';
-import { createPasskey } from '../../passkey/PasskeyModule';
-import config from '../../auth0-configuration';
-
-const MyAccountScreen = () => {
- const { getApiCredentials, myAccount } = useAuth0();
-
- const [loading, setLoading] = useState(false);
- const [apiResult, setApiResult] = useState(null);
- const [apiError, setApiError] = useState(null);
- const [challengeState, setChallengeState] = useState<{
- authenticationMethodId: string;
- authSession: string;
- authParamsPublicKey: Record;
- } | null>(null);
- const [enrollmentState, setEnrollmentState] = useState<{
- id: string;
- authSession: string;
- } | null>(null);
- const [otpCode, setOtpCode] = useState('');
- const [phoneNumber, setPhoneNumber] = useState('');
- const [emailAddress, setEmailAddress] = useState('');
- const [methodId, setMethodId] = useState('');
- const [methodName, setMethodName] = useState('');
-
- const handleError = (e: any) => {
- if (e instanceof PasskeyError) {
- switch (e.type) {
- case PasskeyErrorCodes.USER_CANCELLED:
- Alert.alert('Cancelled', 'You dismissed the passkey prompt.');
- break;
- case PasskeyErrorCodes.NOT_AVAILABLE:
- Alert.alert(
- 'Not Available',
- 'Passkeys are not supported on this device.'
- );
- break;
- default:
- Alert.alert('Passkey Error', `[${e.type}] ${e.message}`);
- }
- } else if (e instanceof MyAccountError) {
- Alert.alert(e.title || 'My Account Error', e.detail || e.message);
- } else {
- Alert.alert('Error', (e as Error).message);
- }
- setApiError(e as Error);
- };
-
- const getMyAccountAccessToken = async (): Promise => {
- const credentials = await getApiCredentials(
- `https://${config.domain}/me/`,
- 'read:me:authentication_methods delete:me:authentication_methods update:me:authentication_methods read:me:factors create:me:authentication_methods'
- );
- return credentials.accessToken;
- };
-
- // --- Passkey Enrollment ---
-
- const onPasskeyEnrollmentChallenge = async () => {
- setApiError(null);
- setApiResult(null);
- setChallengeState(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const challenge = await myAccount.passkeyEnrollmentChallenge({
- accessToken,
- });
-
- setChallengeState(challenge);
- setApiResult({
- step: 'passkeyEnrollmentChallenge',
- authenticationMethodId: challenge.authenticationMethodId,
- authSession: challenge.authSession,
- });
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- const onPasskeyEnrollmentVerify = async () => {
- if (!challengeState) {
- Alert.alert('Error', 'Run Enrollment Challenge first.');
- return;
- }
-
- setApiError(null);
- setLoading(true);
- try {
- const credentialJson = await createPasskey(
- challengeState.authParamsPublicKey
- );
-
- const accessToken = await getMyAccountAccessToken();
- const method = await myAccount.enrollPasskey({
- accessToken,
- authenticationMethodId: challengeState.authenticationMethodId,
- authSession: challengeState.authSession,
- authResponse: credentialJson,
- authParamsPublicKey: challengeState.authParamsPublicKey,
- });
-
- setChallengeState(null);
- setApiResult({
- step: 'passkeyEnrollmentVerify',
- id: method.id,
- type: method.type,
- keyId: method.keyId,
- credentialDeviceType: method.credentialDeviceType,
- credentialBackedUp: method.credentialBackedUp,
- relyingPartyId: method.relyingPartyId,
- createdAt: method.createdAt,
- });
- Alert.alert('Success', 'Passkey enrolled successfully!');
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Phone Enrollment ---
-
- const onEnrollPhone = async () => {
- if (!phoneNumber.trim()) {
- Alert.alert('Error', 'Please enter a phone number.');
- return;
- }
- setApiError(null);
- setApiResult(null);
- setEnrollmentState(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const challenge = await myAccount.enrollPhone({
- accessToken,
- phoneNumber: phoneNumber.trim(),
- preferredAuthenticationMethod: PreferredAuthenticationMethods.SMS,
- });
- setEnrollmentState(challenge);
- setApiResult({ step: 'enrollPhone', ...challenge });
- Alert.alert('OTP Sent', 'Check your phone for the verification code.');
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Email Enrollment ---
-
- const onEnrollEmail = async () => {
- if (!emailAddress.trim()) {
- Alert.alert('Error', 'Please enter an email address.');
- return;
- }
- setApiError(null);
- setApiResult(null);
- setEnrollmentState(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const challenge = await myAccount.enrollEmail({
- accessToken,
- emailAddress: emailAddress.trim(),
- });
- setEnrollmentState(challenge);
- setApiResult({ step: 'enrollEmail', ...challenge });
- Alert.alert('OTP Sent', 'Check your email for the verification code.');
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- TOTP Enrollment ---
-
- const onEnrollTOTP = async () => {
- setApiError(null);
- setApiResult(null);
- setEnrollmentState(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const challenge = await myAccount.enrollTOTP({ accessToken });
- setEnrollmentState({
- id: challenge.id,
- authSession: challenge.authSession,
- });
- setApiResult({
- step: 'enrollTOTP',
- id: challenge.id,
- barcodeUri: challenge.barcodeUri,
- manualInputCode: challenge.manualInputCode,
- });
- Alert.alert(
- 'TOTP Enrolled',
- 'Scan the QR code with your authenticator app, then confirm with OTP.'
- );
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Recovery Code Enrollment ---
-
- const onEnrollRecoveryCode = async () => {
- setApiError(null);
- setApiResult(null);
- setEnrollmentState(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const challenge = await myAccount.enrollRecoveryCode({ accessToken });
- setEnrollmentState({
- id: challenge.id,
- authSession: challenge.authSession,
- });
- setApiResult({
- step: 'enrollRecoveryCode',
- id: challenge.id,
- recoveryCode: challenge.recoveryCode,
- });
- Alert.alert(
- 'Recovery Code',
- `Store this code securely: ${challenge.recoveryCode}`
- );
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Confirm Enrollment with OTP ---
-
- const onConfirmEnrollment = async () => {
- if (!enrollmentState) {
- Alert.alert('Error', 'Start an enrollment first.');
- return;
- }
- if (!otpCode.trim()) {
- Alert.alert('Error', 'Please enter the OTP code.');
- return;
- }
- setApiError(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const method = await myAccount.confirmPhoneEnrollment({
- accessToken,
- id: enrollmentState.id,
- authSession: enrollmentState.authSession,
- otpCode: otpCode.trim(),
- });
- setEnrollmentState(null);
- setOtpCode('');
- setApiResult({ step: 'confirmEnrollment', ...method });
- Alert.alert('Success', 'Enrollment confirmed!');
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Confirm Recovery Code Enrollment ---
-
- const onConfirmRecoveryCode = async () => {
- if (!enrollmentState) {
- Alert.alert('Error', 'Start a recovery code enrollment first.');
- return;
- }
- setApiError(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const method = await myAccount.confirmRecoveryCodeEnrollment({
- accessToken,
- id: enrollmentState.id,
- authSession: enrollmentState.authSession,
- });
- setEnrollmentState(null);
- setApiResult({ step: 'confirmRecoveryCode', ...method });
- Alert.alert('Success', 'Recovery code enrollment confirmed!');
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Get Factors ---
-
- const onGetFactors = async () => {
- setApiError(null);
- setApiResult(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const factors = await myAccount.getFactors({ accessToken });
- setApiResult({ step: 'getFactors', factors });
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Get Authentication Methods ---
-
- const onGetAuthenticationMethods = async () => {
- setApiError(null);
- setApiResult(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const methods = await myAccount.getAuthenticationMethods({ accessToken });
- setApiResult({
- step: 'getAuthenticationMethods',
- count: methods.length,
- methods,
- });
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Update Authentication Method ---
-
- const onUpdateAuthenticationMethod = async () => {
- if (!methodId.trim()) {
- Alert.alert('Error', 'Please enter an authentication method ID.');
- return;
- }
- setApiError(null);
- setApiResult(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- const method = await myAccount.updateAuthenticationMethodById({
- accessToken,
- id: methodId.trim(),
- name: methodName.trim() || undefined,
- });
- setApiResult({ step: 'updateAuthenticationMethodById', ...method });
- Alert.alert('Success', 'Authentication method updated!');
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- };
-
- // --- Delete Authentication Method ---
-
- const onDeleteAuthenticationMethod = async () => {
- if (!methodId.trim()) {
- Alert.alert('Error', 'Please enter an authentication method ID.');
- return;
- }
- Alert.alert(
- 'Confirm Delete',
- `Are you sure you want to delete method ${methodId.trim()}?`,
- [
- { text: 'Cancel', style: 'cancel' },
- {
- text: 'Delete',
- style: 'destructive',
- onPress: async () => {
- setApiError(null);
- setApiResult(null);
- setLoading(true);
- try {
- const accessToken = await getMyAccountAccessToken();
- await myAccount.deleteAuthenticationMethodById({
- accessToken,
- id: methodId.trim(),
- });
- setApiResult({
- step: 'deleteAuthenticationMethodById',
- deleted: methodId.trim(),
- });
- setMethodId('');
- Alert.alert('Success', 'Authentication method deleted!');
- } catch (e) {
- handleError(e);
- } finally {
- setLoading(false);
- }
- },
- },
- ]
- );
- };
-
- if (Platform.OS === 'web') {
- return (
-
-
-
-
- My Account API is only available on native platforms.
-
-
-
- );
- }
-
- return (
-
-
-
-
- Manage authentication methods for the currently authenticated user via
- the My Account API.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- After enrolling phone, email, or TOTP, confirm with the OTP code.
-
-
-
-
-
-
-
-
-
- Enter an authentication method ID to update or delete it.
-
-
-
-
-
-
-
- {enrollmentState && (
-
- Pending Enrollment:
-
- id: {enrollmentState.id}
- {'\n'}authSession: {enrollmentState.authSession.substring(0, 20)}
- ...
-
-
- )}
-
-
- );
-};
-
-const Section = ({
- title,
- children,
-}: {
- title: string;
- children: React.ReactNode;
-}) => (
-
- {title}
- {children}
-
-);
-
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#FFFFFF' },
- content: { padding: 16, gap: 16 },
- description: { fontSize: 14, color: '#666', textAlign: 'center' },
- section: {
- borderWidth: 1,
- borderColor: '#E0E0E0',
- borderRadius: 8,
- padding: 16,
- gap: 10,
- },
- sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 4 },
- sectionDescription: { fontSize: 13, color: '#666', marginBottom: 4 },
- input: {
- borderWidth: 1,
- borderColor: '#CCC',
- borderRadius: 6,
- padding: 10,
- fontSize: 14,
- },
- resultBox: {
- backgroundColor: '#F5F5F5',
- borderRadius: 6,
- padding: 10,
- gap: 4,
- },
- resultLabel: { fontSize: 12, fontWeight: '600', color: '#333' },
- resultValue: { fontSize: 11, color: '#555', fontFamily: 'monospace' },
-});
-
-export default MyAccountScreen;
diff --git a/example/src/screens/hooks/Profile.tsx b/example/src/screens/hooks/Profile.tsx
deleted file mode 100644
index f9855cce8..000000000
--- a/example/src/screens/hooks/Profile.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import React, { useState } from 'react';
-import { ScrollView, View, StyleSheet, Alert } from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import { useAuth0 } from 'react-native-auth0';
-import Button from '../../components/Button';
-import Header from '../../components/Header';
-import UserInfo from '../../components/UserInfo';
-import Result from '../../components/Result';
-
-const ProfileScreen = () => {
- const {
- user,
- clearSession,
- clearCredentials,
- getCredentials,
- hasValidCredentials,
- revokeRefreshToken,
- } = useAuth0();
- const [credentials, setCredentials] = useState(null);
- const [apiError, setApiError] = useState(null);
- const [apiResult, setApiResult] = useState(null);
-
- const onLogout = async () => {
- try {
- // clearSession will log the user out of the session
- await clearSession();
- // Clear any local state if needed
- setCredentials(null);
- setApiResult(null);
- setApiError(null);
- } catch (e) {
- console.log('Logout error: ', e);
- // Show error to user
- Alert.alert(
- 'Logout Error',
- `Failed to logout: ${e.message || 'Unknown error'}`,
- [{ text: 'OK' }]
- );
- setApiError(e as Error);
- }
- };
-
- const onClearCredentials = async () => {
- try {
- await clearCredentials();
- // Clear any local state
- setCredentials(null);
- setApiResult({ success: 'Credentials cleared locally' });
- setApiError(null);
- Alert.alert(
- 'Success',
- 'Credentials have been cleared from local storage.'
- );
- } catch (e) {
- console.log('Clear credentials error: ', e);
- setApiError(e as Error);
- Alert.alert(
- 'Error',
- `Failed to clear credentials: ${e.message || 'Unknown error'}`,
- [{ text: 'OK' }]
- );
- }
- };
-
- const onGetCredentials = async () => {
- try {
- const result = await getCredentials();
- setCredentials(result ?? null);
- setApiResult(result ?? null);
- setApiError(null);
- } catch (e) {
- setApiError(e as Error);
- }
- };
-
- const onCheckCredentials = async () => {
- try {
- const isValid = await hasValidCredentials();
- setApiResult({ hasValidCredentials: isValid });
- setApiError(null);
- } catch (e) {
- setApiError(e as Error);
- }
- };
-
- const onRevokeToken = async () => {
- try {
- if (!credentials?.refreshToken) {
- Alert.alert(
- 'Error',
- 'No refresh token found. Please get credentials first.'
- );
- return;
- }
- await revokeRefreshToken({ refreshToken: credentials.refreshToken });
- setApiResult({ success: 'Refresh token revoked' });
- setApiError(null);
- } catch (e) {
- setApiError(e as Error);
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#FFFFFF',
- },
- content: {
- alignItems: 'center',
- padding: 16,
- },
- spacer: {
- height: 16,
- },
- clearCredentialsButton: {
- backgroundColor: '#FF9800',
- },
- logoutButton: {
- backgroundColor: '#424242',
- },
-});
-
-export default ProfileScreen;
diff --git a/example/src/shared/ResultView.tsx b/example/src/shared/ResultView.tsx
new file mode 100644
index 000000000..99d6e8106
--- /dev/null
+++ b/example/src/shared/ResultView.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { Text, View } from 'react-native';
+
+type Props = {
+ result?: unknown;
+ error?: Error | null;
+};
+
+// Minimal result/error display. No styling beyond spacing, per the example's
+// "default React Native components only" convention.
+const ResultView = ({ result, error }: Props) => {
+ if (!result && !error) {
+ return null;
+ }
+ return (
+
+ {error ? (
+ Error: {error.message}
+ ) : (
+ {format(result)}
+ )}
+
+ );
+};
+
+const format = (value: unknown): string => {
+ if (typeof value === 'string') {
+ return value;
+ }
+ try {
+ return JSON.stringify(value, null, 2);
+ } catch {
+ return String(value);
+ }
+};
+
+export default ResultView;
diff --git a/example/src/shared/Section.tsx b/example/src/shared/Section.tsx
new file mode 100644
index 000000000..5a87ae5ca
--- /dev/null
+++ b/example/src/shared/Section.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Text, View } from 'react-native';
+
+type Props = {
+ title: string;
+ children: React.ReactNode;
+};
+
+// A feature section: a heading followed by its controls, separated by spacing.
+const Section = ({ title, children }: Props) => (
+
+ {title}
+ {children}
+
+);
+
+export default Section;
diff --git a/example/src/shared/api.ts b/example/src/shared/api.ts
new file mode 100644
index 000000000..a89350c70
--- /dev/null
+++ b/example/src/shared/api.ts
@@ -0,0 +1,16 @@
+import Auth0 from 'react-native-auth0';
+import config from '../auth0-configuration';
+
+if (!config.domain || !config.clientId) {
+ throw new Error(
+ 'Missing Auth0 credentials. Set domain and clientId in src/auth0-configuration.js.'
+ );
+}
+
+// Shared Auth0 class instance used by the *Class.tsx reference files.
+const auth0 = new Auth0({
+ domain: config.domain,
+ clientId: config.clientId,
+});
+
+export default auth0;
diff --git a/example/webpack.config.js b/example/webpack.config.js
index c38fe263b..77c66439a 100644
--- a/example/webpack.config.js
+++ b/example/webpack.config.js
@@ -15,10 +15,7 @@ const babelLoaderConfiguration = {
path.resolve(appDirectory, 'index.js'),
path.resolve(appDirectory, 'src'),
path.resolve(appDirectory, '../src'), // Included react-native-auth0 source
- path.resolve(__dirname, 'node_modules/@react-navigation'),
path.resolve(__dirname, 'node_modules/react-native-safe-area-context'),
- path.resolve(__dirname, 'node_modules/react-native-screens'),
- path.resolve(__dirname, 'node_modules/react-native-vector-icons'),
],
use: {
loader: 'babel-loader',
diff --git a/yarn.lock b/yarn.lock
index b797a4512..628f1f4d2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4198,105 +4198,6 @@ __metadata:
languageName: node
linkType: hard
-"@react-navigation/bottom-tabs@npm:^7.18.15":
- version: 7.18.16
- resolution: "@react-navigation/bottom-tabs@npm:7.18.16"
- dependencies:
- "@react-navigation/elements": "npm:^2.9.38"
- color: "npm:^4.2.3"
- sf-symbols-typescript: "npm:^2.1.0"
- peerDependencies:
- "@react-navigation/native": ^7.3.16
- react: ">= 18.2.0"
- react-native: "*"
- react-native-safe-area-context: ">= 4.0.0"
- react-native-screens: ">= 4.0.0"
- checksum: 10c0/868ee3fa4353c85da1f32f1c10ffab8b94cff92fd664476e96c10e26d3d2df4a5e238bf3fc463e7f77ff1d895dc68e226b5af325bf22aaeea057365eb42139ca
- languageName: node
- linkType: hard
-
-"@react-navigation/core@npm:^7.21.12":
- version: 7.21.12
- resolution: "@react-navigation/core@npm:7.21.12"
- dependencies:
- "@react-navigation/routers": "npm:^7.6.4"
- escape-string-regexp: "npm:^4.0.0"
- fast-deep-equal: "npm:^3.1.3"
- nanoid: "npm:^3.3.11"
- query-string: "npm:^7.1.3"
- react-is: "npm:^19.1.0"
- use-latest-callback: "npm:^0.2.4"
- use-sync-external-store: "npm:^1.5.0"
- peerDependencies:
- react: ">= 18.2.0"
- checksum: 10c0/2e05ff5f251f64e172fccc2d80e7c76b6249c8fe0198f3013045c27c76bac1813b1c676d659df8002673e196a8d4f9d91ab8af5df76873c55494e45a033dcc98
- languageName: node
- linkType: hard
-
-"@react-navigation/elements@npm:^2.9.38":
- version: 2.9.38
- resolution: "@react-navigation/elements@npm:2.9.38"
- dependencies:
- color: "npm:^4.2.3"
- use-latest-callback: "npm:^0.2.4"
- use-sync-external-store: "npm:^1.5.0"
- peerDependencies:
- "@react-native-masked-view/masked-view": ">= 0.2.0"
- "@react-navigation/native": ^7.3.16
- react: ">= 18.2.0"
- react-native: "*"
- react-native-safe-area-context: ">= 4.0.0"
- peerDependenciesMeta:
- "@react-native-masked-view/masked-view":
- optional: true
- checksum: 10c0/8aad1be751f0d4071205ce905f8e6794d9467cae83f4102a622949ec43b79d367825cfe81da20294d5c1a707d91b8128eed8b6b320a4ad93c88db28fbeb5e560
- languageName: node
- linkType: hard
-
-"@react-navigation/native@npm:^7.3.15":
- version: 7.3.16
- resolution: "@react-navigation/native@npm:7.3.16"
- dependencies:
- "@react-navigation/core": "npm:^7.21.12"
- escape-string-regexp: "npm:^4.0.0"
- fast-deep-equal: "npm:^3.1.3"
- nanoid: "npm:^3.3.11"
- standard-navigation: "npm:^0.0.8"
- use-latest-callback: "npm:^0.2.4"
- peerDependencies:
- react: ">= 18.2.0"
- react-native: "*"
- checksum: 10c0/86193e9ee0b2e64e9148691b268b2a9e9769ac62ad58a281f09db79da171232d4bee736f67df97d6d1159909a96bf07c84de9bf8ddb346affb01b80e9cb8f8cd
- languageName: node
- linkType: hard
-
-"@react-navigation/routers@npm:^7.6.4":
- version: 7.6.4
- resolution: "@react-navigation/routers@npm:7.6.4"
- dependencies:
- nanoid: "npm:^3.3.11"
- checksum: 10c0/eca376de83ed618d637c40ca6946f94de2c8bafe4edeaf873708f6a2a45759320692a9e86e1a5e53c2919594cc20b170775c2de22b947114cf125f66f5051b6c
- languageName: node
- linkType: hard
-
-"@react-navigation/stack@npm:^7.10.20":
- version: 7.10.22
- resolution: "@react-navigation/stack@npm:7.10.22"
- dependencies:
- "@react-navigation/elements": "npm:^2.9.38"
- color: "npm:^4.2.3"
- use-latest-callback: "npm:^0.2.4"
- peerDependencies:
- "@react-navigation/native": ^7.3.16
- react: ">= 18.2.0"
- react-native: "*"
- react-native-gesture-handler: ">= 2.0.0"
- react-native-safe-area-context: ">= 4.0.0"
- react-native-screens: ">= 4.0.0"
- checksum: 10c0/3f5223c620a8c69a17def2b0a0ba74d7458ecbd898f3d329bc567aa6513e8fe974e0924156d504dd02a942af6ec3ce96531d4547347422640b282d83cb2f8bf8
- languageName: node
- linkType: hard
-
"@release-it/conventional-changelog@npm:^11.0.1":
version: 11.0.1
resolution: "@release-it/conventional-changelog@npm:11.0.1"
@@ -5286,9 +5187,6 @@ __metadata:
"@react-native/babel-preset": "npm:0.86.2"
"@react-native/metro-config": "npm:0.86.2"
"@react-native/typescript-config": "npm:0.86.2"
- "@react-navigation/bottom-tabs": "npm:^7.18.15"
- "@react-navigation/native": "npm:^7.3.15"
- "@react-navigation/stack": "npm:^7.10.20"
"@types/react": "npm:^19.2.18"
babel-loader: "npm:^10.1.1"
babel-plugin-react-native-web: "npm:^0.21.2"
@@ -5296,10 +5194,8 @@ __metadata:
react: "npm:19.2.8"
react-native: "npm:0.86.2"
react-native-builder-bob: "npm:^0.43.0"
- react-native-gesture-handler: "npm:^3.0.2"
react-native-monorepo-config: "npm:^0.4.0"
react-native-safe-area-context: "npm:^5.8.1"
- react-native-screens: "npm:^4.27.0"
react-native-web: "npm:^0.21.2"
url-loader: "npm:^4.1.1"
webpack: "npm:^5.109.2"
@@ -6771,33 +6667,13 @@ __metadata:
languageName: node
linkType: hard
-"color-name@npm:^1.0.0, color-name@npm:~1.1.4":
+"color-name@npm:~1.1.4":
version: 1.1.4
resolution: "color-name@npm:1.1.4"
checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95
languageName: node
linkType: hard
-"color-string@npm:^1.9.0":
- version: 1.9.1
- resolution: "color-string@npm:1.9.1"
- dependencies:
- color-name: "npm:^1.0.0"
- simple-swizzle: "npm:^0.2.2"
- checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404
- languageName: node
- linkType: hard
-
-"color@npm:^4.2.3":
- version: 4.2.3
- resolution: "color@npm:4.2.3"
- dependencies:
- color-convert: "npm:^2.0.1"
- color-string: "npm:^1.9.0"
- checksum: 10c0/7fbe7cfb811054c808349de19fb380252e5e34e61d7d168ec3353e9e9aacb1802674bddc657682e4e9730c2786592a4de6f8283e7e0d3870b829bb0b7b2f6118
- languageName: node
- linkType: hard
-
"colorette@npm:^1.0.7":
version: 1.4.0
resolution: "colorette@npm:1.4.0"
@@ -7393,13 +7269,6 @@ __metadata:
languageName: node
linkType: hard
-"decode-uri-component@npm:^0.2.2":
- version: 0.2.2
- resolution: "decode-uri-component@npm:0.2.2"
- checksum: 10c0/1f4fa54eb740414a816b3f6c24818fbfcabd74ac478391e9f4e2282c994127db02010ce804f3d08e38255493cfe68608b3f5c8e09fd6efc4ae46c807691f7a31
- languageName: node
- linkType: hard
-
"dedent@npm:^1.0.0, dedent@npm:^1.7.2":
version: 1.7.2
resolution: "dedent@npm:1.7.2"
@@ -8944,13 +8813,6 @@ __metadata:
languageName: node
linkType: hard
-"filter-obj@npm:^1.1.0":
- version: 1.1.0
- resolution: "filter-obj@npm:1.1.0"
- checksum: 10c0/071e0886b2b50238ca5026c5bbf58c26a7c1a1f720773b8c7813d16ba93d0200de977af14ac143c5ac18f666b2cfc83073f3a5fe6a4e996c49e0863d5500fccf
- languageName: node
- linkType: hard
-
"finalhandler@npm:1.1.2":
version: 1.1.2
resolution: "finalhandler@npm:1.1.2"
@@ -10050,13 +9912,6 @@ __metadata:
languageName: node
linkType: hard
-"is-arrayish@npm:^0.3.1":
- version: 0.3.4
- resolution: "is-arrayish@npm:0.3.4"
- checksum: 10c0/1fa672a2f0bedb74154440310f616c0b6e53a95cf0625522ae050f06626d1cabd1a3d8085c882dc45c61ad0e7df2529aff122810b3b4a552880bf170d6df94e0
- languageName: node
- linkType: hard
-
"is-async-function@npm:^2.0.0":
version: 2.1.1
resolution: "is-async-function@npm:2.1.1"
@@ -12339,7 +12194,7 @@ __metadata:
languageName: node
linkType: hard
-"nanoid@npm:^3.3.11, nanoid@npm:^3.3.17":
+"nanoid@npm:^3.3.17":
version: 3.3.17
resolution: "nanoid@npm:3.3.17"
bin:
@@ -13543,18 +13398,6 @@ __metadata:
languageName: node
linkType: hard
-"query-string@npm:^7.1.3":
- version: 7.1.3
- resolution: "query-string@npm:7.1.3"
- dependencies:
- decode-uri-component: "npm:^0.2.2"
- filter-obj: "npm:^1.1.0"
- split-on-first: "npm:^1.0.0"
- strict-uri-encode: "npm:^2.0.0"
- checksum: 10c0/a896c08e9e0d4f8ffd89a572d11f668c8d0f7df9c27c6f49b92ab31366d3ba0e9c331b9a620ee747893436cd1f2f821a6327e2bc9776bde2402ac6c270b801b2
- languageName: node
- linkType: hard
-
"querystringify@npm:^2.1.1":
version: 2.2.0
resolution: "querystringify@npm:2.2.0"
@@ -13654,15 +13497,6 @@ __metadata:
languageName: node
linkType: hard
-"react-freeze@npm:^1.0.0":
- version: 1.0.4
- resolution: "react-freeze@npm:1.0.4"
- peerDependencies:
- react: ">=17.0.0"
- checksum: 10c0/8f51257c261bfefff86f618e958683536248f708019632d309ee5ebdd52f25d3c130660d06fb6f0f4fdef79f00f8ec7177233a872c2321f7d46b7e77ccc522a1
- languageName: node
- linkType: hard
-
"react-is@npm:^16.13.1":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@@ -13684,13 +13518,6 @@ __metadata:
languageName: node
linkType: hard
-"react-is@npm:^19.1.0":
- version: 19.2.8
- resolution: "react-is@npm:19.2.8"
- checksum: 10c0/ed5322c84efe035c8fc814b1614ff5ca7fb8c2872a7c045197daf99f9b1bd68f32a2c79ffcbcfcff2fc5d93924ff9f9447400898f5b1534e6302bb0736a257f0
- languageName: node
- linkType: hard
-
"react-native-auth0@workspace:.":
version: 0.0.0-use.local
resolution: "react-native-auth0@workspace:."
@@ -13791,19 +13618,6 @@ __metadata:
languageName: node
linkType: hard
-"react-native-gesture-handler@npm:^3.0.2":
- version: 3.2.1
- resolution: "react-native-gesture-handler@npm:3.2.1"
- dependencies:
- "@types/react-test-renderer": "npm:^19.1.0"
- invariant: "npm:^2.2.4"
- peerDependencies:
- react: "*"
- react-native: "*"
- checksum: 10c0/e20537d1eafe35206494eae7885ef85e7cd85185b32ccaac3d25f3e87beafa007ac79556c0af8da245db1140f6b5df35d20509d7fa56301d730de5a8abb1b90f
- languageName: node
- linkType: hard
-
"react-native-monorepo-config@npm:^0.4.0":
version: 0.4.0
resolution: "react-native-monorepo-config@npm:0.4.0"
@@ -13824,19 +13638,6 @@ __metadata:
languageName: node
linkType: hard
-"react-native-screens@npm:^4.27.0":
- version: 4.27.0
- resolution: "react-native-screens@npm:4.27.0"
- dependencies:
- react-freeze: "npm:^1.0.0"
- warn-once: "npm:^0.1.0"
- peerDependencies:
- react: "*"
- react-native: "*"
- checksum: 10c0/357674b881189ff8933f457acadbfc3030a376d0ccdfb4b6005462aabbf38287d4d0f39a301cb94c45df6c2e09be28bcfc4d71298c8d34b8ac87ebe639e46ff9
- languageName: node
- linkType: hard
-
"react-native-web@npm:^0.21.2":
version: 0.21.2
resolution: "react-native-web@npm:0.21.2"
@@ -14577,13 +14378,6 @@ __metadata:
languageName: node
linkType: hard
-"sf-symbols-typescript@npm:^2.1.0":
- version: 2.2.0
- resolution: "sf-symbols-typescript@npm:2.2.0"
- checksum: 10c0/3f3bbf33aaad19e619d6f169899b39e9fe9c5fd21f0d6d511100e36887606ad349109ddc6ff82933f2b8cbf437dd7105c2ae6b0059b291dc47f143b30c2074cc
- languageName: node
- linkType: hard
-
"shallow-clone@npm:^3.0.0":
version: 3.0.1
resolution: "shallow-clone@npm:3.0.1"
@@ -14689,15 +14483,6 @@ __metadata:
languageName: node
linkType: hard
-"simple-swizzle@npm:^0.2.2":
- version: 0.2.4
- resolution: "simple-swizzle@npm:0.2.4"
- dependencies:
- is-arrayish: "npm:^0.3.1"
- checksum: 10c0/846c3fdd1325318d5c71295cfbb99bfc9edc4c8dffdda5e6e9efe30482bbcd32cf360fc2806f46ac43ff7d09bcfaff20337bb79f826f0e6a8e366efd3cdd7868
- languageName: node
- linkType: hard
-
"sisteransi@npm:^1.0.5":
version: 1.0.5
resolution: "sisteransi@npm:1.0.5"
@@ -14878,13 +14663,6 @@ __metadata:
languageName: node
linkType: hard
-"split-on-first@npm:^1.0.0":
- version: 1.1.0
- resolution: "split-on-first@npm:1.1.0"
- checksum: 10c0/56df8344f5a5de8521898a5c090023df1d8b8c75be6228f56c52491e0fc1617a5236f2ac3a066adb67a73231eac216ccea7b5b4a2423a543c277cb2f48d24c29
- languageName: node
- linkType: hard
-
"sprintf-js@npm:~1.0.2":
version: 1.0.3
resolution: "sprintf-js@npm:1.0.3"
@@ -14917,15 +14695,6 @@ __metadata:
languageName: node
linkType: hard
-"standard-navigation@npm:^0.0.8":
- version: 0.0.8
- resolution: "standard-navigation@npm:0.0.8"
- peerDependencies:
- react: "*"
- checksum: 10c0/46cdde7d565a612d85a8f479b18467fcd1a0d599c047d70fa50b2355967cc29903c20e1f9f7ed77eea56f55fbaf144ff2d2820389dd6e0ee0e15b162116e69fe
- languageName: node
- linkType: hard
-
"statuses@npm:>= 1.5.0 < 2, statuses@npm:~1.5.0":
version: 1.5.0
resolution: "statuses@npm:1.5.0"
@@ -14964,13 +14733,6 @@ __metadata:
languageName: node
linkType: hard
-"strict-uri-encode@npm:^2.0.0":
- version: 2.0.0
- resolution: "strict-uri-encode@npm:2.0.0"
- checksum: 10c0/010cbc78da0e2cf833b0f5dc769e21ae74cdc5d5f5bd555f14a4a4876c8ad2c85ab8b5bdf9a722dc71a11dcd3184085e1c3c0bd50ec6bb85fffc0f28cf82597d
- languageName: node
- linkType: hard
-
"string-length@npm:^4.0.1":
version: 4.0.2
resolution: "string-length@npm:4.0.2"
@@ -15865,24 +15627,6 @@ __metadata:
languageName: node
linkType: hard
-"use-latest-callback@npm:^0.2.4":
- version: 0.2.6
- resolution: "use-latest-callback@npm:0.2.6"
- peerDependencies:
- react: ">=16.8"
- checksum: 10c0/6523747b2d76f12a91cf80a3cd9803449571e9defa8db69e9a03b8199b211127d88c038063714fe31d3c2e63ca51a491bd05f4e34203795a1c692a5a44416610
- languageName: node
- linkType: hard
-
-"use-sync-external-store@npm:^1.5.0":
- version: 1.6.0
- resolution: "use-sync-external-store@npm:1.6.0"
- peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b
- languageName: node
- linkType: hard
-
"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"
@@ -15989,13 +15733,6 @@ __metadata:
languageName: node
linkType: hard
-"warn-once@npm:^0.1.0":
- version: 0.1.1
- resolution: "warn-once@npm:0.1.1"
- checksum: 10c0/f531e7b2382124f51e6d8f97b8c865246db8ab6ff4e53257a2d274e0f02b97d7201eb35db481843dc155815e154ad7afb53b01c4d4db15fb5aa073562496aff7
- languageName: node
- linkType: hard
-
"watchpack@npm:^2.5.2":
version: 2.5.2
resolution: "watchpack@npm:2.5.2"