> ## Documentation Index
> Fetch the complete documentation index at: https://auth0-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Login to Your Expo Application

> This guide demonstrates how to integrate Auth0 with any Expo app using the Auth0 React Native SDK.

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

<HowToSchema />

<Tip>
  This Quickstart is for Expo applications. To integrate Auth0 into your React Native application, refer to the [React Native Quickstart](https://auth0.com/docs/quickstart/native/react-native/interactive).
</Tip>

<Accordion title="Use AI to integrate Auth0" icon="microchip-ai" iconType="solid" defaultOpen>
  If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 authentication automatically in minutes using [agent skills](https://agentskills.io/home).

  **Install:**

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-expo
  ```

  **Then ask your AI assistant:**

  ```text theme={null}
  Add Auth0 authentication to my Expo app
  ```

  Your AI assistant will automatically create your Auth0 application, fetch credentials, install the react-native-auth0 SDK, configure the Expo plugin, and implement login/logout flows. [Full agent skills documentation →](/docs/quickstart/agent-skills)
</Accordion>

## Get Started

<Steps>
  <Step title="Create a new Expo project" stepNumber={1}>
    Create a new Expo project for this quickstart.

    **In your terminal:**

    ```bash theme={null}
    npx create-expo-app Auth0ExpoSample --template blank
    cd Auth0ExpoSample
    ```

    <Tip>
      This creates a minimal Expo app with the latest SDK, ready for native module integration. The `--template blank` flag provides a clean starting point without additional boilerplate.
    </Tip>

    <Warning>
      This SDK is **NOT compatible with Expo Go** because it requires custom native code. You must use `npx expo run:ios` or `npx expo run:android` to create a development build.
    </Warning>
  </Step>

  <Step title="Install the Auth0 SDK" stepNumber={2}>
    Add the Auth0 React Native SDK to your project.

    ```bash theme={null}
    npx expo install react-native-auth0
    ```

    <Tip>
      Using `npx expo install` ensures version compatibility with your Expo SDK version. The SDK auto-configures through the Expo plugin system.
    </Tip>
  </Step>

  <Step title="Configure the Expo Plugin" stepNumber={3}>
    Configure the Auth0 plugin to handle native iOS and Android configuration automatically.

    Update your `app.json` to include the Auth0 plugin:

    ```json app.json lines theme={null}
    {
      "expo": {
        "name": "Auth0ExpoSample",
        "slug": "auth0-expo-sample",
        "version": "1.0.0",
        "ios": {
          "bundleIdentifier": "com.auth0.samples.expo",
          "supportsTablet": true
        },
        "android": {
          "package": "com.auth0.samples.expo",
          "adaptiveIcon": {
            "foregroundImage": "./assets/images/adaptive-icon.png",
            "backgroundColor": "#ffffff"
          }
        },
        "plugins": [
          [
            "react-native-auth0",
            {
              "domain": "{yourDomain}",
              "customScheme": "auth0sample"
            }
          ]
        ]
      }
    }
    ```

    Replace `{yourDomain}` with your Auth0 domain (you'll get this in the next step).

    <Warning>
      **Important**: You must define `bundleIdentifier` for iOS and `package` for Android in your `app.json`. These identifiers are required for the Auth0 SDK to configure the native projects properly. If you don't specify a custom scheme, the SDK will use the bundle identifier as the URL scheme.
    </Warning>

    <Info>
      The `customScheme` must be lowercase with no special characters. This value is used to construct callback URLs and must be passed to `authorize()` and `clearSession()` methods.
    </Info>
  </Step>

  <Step title="Configure your Auth0 Application" stepNumber={4}>
    Create and configure an Auth0 application to work with your Expo app.

    1. Head to the [Auth0 Dashboard](https://manage.auth0.com/dashboard/)
    2. Click on **Applications** > **Applications** > **Create Application**
    3. In the popup, enter a name for your app (e.g., `Auth0 Expo Sample`), select `Native` as the app type and click **Create**
    4. Switch to the **Settings** tab on the Application Details page
    5. Note your **Domain** and **Client ID** values
    6. Update the `domain` value in your `app.json` plugin configuration with your Auth0 domain

    **Allowed Callback URLs:**

    ```
    auth0sample://{yourDomain}/ios/com.auth0.samples.expo/callback,
    auth0sample://{yourDomain}/android/com.auth0.samples.expo/callback
    ```

    **Allowed Logout URLs:**

    ```
    auth0sample://{yourDomain}/ios/com.auth0.samples.expo/callback,
    auth0sample://{yourDomain}/android/com.auth0.samples.expo/callback
    ```

    Replace `{yourDomain}` with your actual Auth0 domain (e.g., `dev-abc123.us.auth0.com`).

    <Info>
      **Allowed Callback URLs** are a critical security measure to ensure users are safely returned to your application after authentication. Without a matching URL, the login process will fail, and users will be blocked by an Auth0 error page instead of accessing your app.

      **Allowed Logout URLs** are essential for providing a seamless user experience upon signing out. Without a matching URL, users will not be redirected back to your application after logout and will instead be left on a generic Auth0 page.

      The callback URL format is: `{customScheme}://{yourDomain}/{platform}/{bundleIdentifier or packageName}/callback`. The URL scheme uses the `customScheme` from your `app.json`, but the path always contains the `bundleIdentifier` (iOS) or `package` (Android) — not the custom scheme. If you don't specify a `customScheme`, the SDK defaults to `{bundleIdentifier}.auth0` / `{packageName}.auth0` as the URL scheme.
    </Info>

    <Warning>
      **Important**: Ensure the `customScheme` in your callback URLs matches exactly with the value in your `app.json` plugin configuration, and that the path contains your actual `bundleIdentifier` (iOS) or `package` (Android). Mismatched values will cause authentication to fail.
    </Warning>
  </Step>

  <Step title="Setup App Component" stepNumber={5}>
    Setup your main app component based on your chosen implementation approach.

    <Tabs>
      <Tab title="Hooks-based (with Provider)">
        Replace the contents of `App.js` and wrap your application with the `Auth0Provider` component:

        ```jsx App.js lines theme={null}
        import React from 'react';
        import {Auth0Provider, useAuth0} from 'react-native-auth0';
        import {
          StyleSheet,
          Text,
          View,
          Button,
          Image,
          ActivityIndicator,
        } from 'react-native';

        function HomeScreen() {
          const {authorize, clearSession, user, isLoading} = useAuth0();

          const handleLogin = async () => {
            try {
              await authorize({customScheme: 'auth0sample', scope: 'openid profile email'});
            } catch (e) {
              console.error('Login error:', e);
            }
          };

          const handleLogout = async () => {
            try {
              await clearSession({customScheme: 'auth0sample'});
            } catch (e) {
              console.error('Logout error:', e);
            }
          };

          if (isLoading) {
            return (
              <View style={styles.container}>
                <ActivityIndicator size="large" color="#0066cc" />
                <Text style={styles.loadingText}>Loading...</Text>
              </View>
            );
          }

          return (
            <View style={styles.container}>
              <Text style={styles.title}>Auth0 Expo Sample</Text>

              {user ? (
                <View style={styles.profileContainer}>
                  {user.picture && (
                    <Image source={{uri: user.picture}} style={styles.avatar} />
                  )}
                  <Text style={styles.welcomeText}>Welcome, {user.name}!</Text>
                  <Text style={styles.emailText}>{user.email}</Text>
                  <View style={styles.buttonContainer}>
                    <Button title="Log Out" onPress={handleLogout} color="#dc3545" />
                  </View>
                </View>
              ) : (
                <View style={styles.loginContainer}>
                  <Text style={styles.subtitle}>
                    Tap the button below to log in
                  </Text>
                  <View style={styles.buttonContainer}>
                    <Button title="Log In" onPress={handleLogin} color="#0066cc" />
                  </View>
                </View>
              )}
            </View>
          );
        }

        export default function App() {
          return (
            <Auth0Provider domain="{yourDomain}" clientId="{yourClientId}">
              <HomeScreen />
            </Auth0Provider>
          );
        }

        const styles = StyleSheet.create({
          container: {
            flex: 1,
            justifyContent: 'center',
            alignItems: 'center',
            padding: 20,
            backgroundColor: '#fff',
          },
          title: {
            fontSize: 28,
            fontWeight: 'bold',
            marginBottom: 20,
            color: '#333',
          },
          subtitle: {
            fontSize: 16,
            color: '#666',
            marginBottom: 30,
            textAlign: 'center',
          },
          loadingText: {
            marginTop: 10,
            fontSize: 16,
            color: '#666',
          },
          profileContainer: {
            alignItems: 'center',
          },
          avatar: {
            width: 100,
            height: 100,
            borderRadius: 50,
            marginBottom: 20,
          },
          welcomeText: {
            fontSize: 22,
            fontWeight: '600',
            marginBottom: 8,
            color: '#333',
          },
          emailText: {
            fontSize: 16,
            color: '#666',
            marginBottom: 30,
          },
          loginContainer: {
            alignItems: 'center',
          },
          buttonContainer: {
            width: 200,
            marginTop: 10,
          },
        });
        ```

        Replace `{yourDomain}` with your Auth0 domain and `{yourClientId}` with your Client ID from the Auth0 Dashboard.

        <Tip>
          The `Auth0Provider` initializes the SDK and provides authentication context to all child components via the `useAuth0` hook. The `customScheme` parameter must match the value in your `app.json` plugin configuration.
        </Tip>
      </Tab>

      <Tab title="Class-based (without Provider)">
        Replace the contents of `App.js` with a class-based component:

        ```jsx App.js lines theme={null}
        import React, {Component} from 'react';
        import {
          View,
          Text,
          Button,
          StyleSheet,
          ActivityIndicator,
          Image,
        } from 'react-native';
        import Auth0, {Credentials} from 'react-native-auth0';

        const auth0 = new Auth0({
          domain: '{yourDomain}',
          clientId: '{yourClientId}',
        });

        interface User {
          name?: string;
          email?: string;
          picture?: string;
        }

        interface AppState {
          user: User | null;
          isLoading: boolean;
        }

        class App extends Component<{}, AppState> {
          constructor(props: {}) {
            super(props);
            this.state = {
              user: null,
              isLoading: true,
            };
          }

          async componentDidMount() {
            await this.checkAuthStatus();
          }

          checkAuthStatus = async () => {
            try {
              const hasValidCredentials = await auth0.credentialsManager.hasValidCredentials();
              if (hasValidCredentials) {
                const credentials = await auth0.credentialsManager.getCredentials();
                const userInfo = await auth0.auth.userInfo({token: credentials.accessToken});
                this.setState({user: userInfo, isLoading: false});
              } else {
                this.setState({isLoading: false});
              }
            } catch (e) {
              console.error(e);
              this.setState({isLoading: false});
            }
          };

          login = async () => {
            try {
              const credentials: Credentials = await auth0.webAuth.authorize({
                scope: 'openid profile email',
              }, {customScheme: 'auth0sample'});
              
              await auth0.credentialsManager.saveCredentials(credentials);
              const userInfo = await auth0.auth.userInfo({token: credentials.accessToken});
              this.setState({user: userInfo});
            } catch (e) {
              console.error(e);
            }
          };

          logout = async () => {
            try {
              await auth0.webAuth.clearSession({}, {customScheme: 'auth0sample'});
              await auth0.credentialsManager.clearCredentials();
              this.setState({user: null});
            } catch (e) {
              console.error(e);
            }
          };

          render() {
            const {user, isLoading} = this.state;

            if (isLoading) {
              return (
                <View style={styles.container}>
                  <ActivityIndicator size="large" color="#0066cc" />
                  <Text style={styles.loadingText}>Loading...</Text>
                </View>
              );
            }

            return (
              <View style={styles.container}>
                <Text style={styles.title}>Auth0 Expo Sample</Text>

                {user ? (
                  <View style={styles.profileContainer}>
                    {user.picture && (
                      <Image source={{uri: user.picture}} style={styles.avatar} />
                    )}
                    <Text style={styles.welcomeText}>Welcome, {user.name}!</Text>
                    <Text style={styles.emailText}>{user.email}</Text>
                    <View style={styles.buttonContainer}>
                      <Button title="Log Out" onPress={this.logout} color="#dc3545" />
                    </View>
                  </View>
                ) : (
                  <View style={styles.loginContainer}>
                    <Text style={styles.subtitle}>
                      Tap the button below to log in
                    </Text>
                    <View style={styles.buttonContainer}>
                      <Button title="Log In" onPress={this.login} color="#0066cc" />
                    </View>
                  </View>
                )}
              </View>
            );
          }
        }

        const styles = StyleSheet.create({
          container: {
            flex: 1,
            justifyContent: 'center',
            alignItems: 'center',
            padding: 20,
            backgroundColor: '#fff',
          },
          title: {
            fontSize: 28,
            fontWeight: 'bold',
            marginBottom: 20,
            color: '#333',
          },
          subtitle: {
            fontSize: 16,
            color: '#666',
            marginBottom: 30,
            textAlign: 'center',
          },
          loadingText: {
            marginTop: 10,
            fontSize: 16,
            color: '#666',
          },
          profileContainer: {
            alignItems: 'center',
          },
          avatar: {
            width: 100,
            height: 100,
            borderRadius: 50,
            marginBottom: 20,
          },
          welcomeText: {
            fontSize: 22,
            fontWeight: '600',
            marginBottom: 8,
            color: '#333',
          },
          emailText: {
            fontSize: 16,
            color: '#666',
            marginBottom: 30,
          },
          loginContainer: {
            alignItems: 'center',
          },
          buttonContainer: {
            width: 200,
            marginTop: 10,
          },
        });

        export default App;
        ```

        Replace `{yourDomain}` with your Auth0 domain and `{yourClientId}` with your Client ID from the Auth0 Dashboard.

        <Tip>
          The class-based approach doesn't require the `Auth0Provider` since it uses the `Auth0` class instance directly. The `customScheme` option must be passed as the second parameter to `authorize()` and `clearSession()`.
        </Tip>
      </Tab>
    </Tabs>

    <Info>
      The `authorize()` method opens Auth0's Universal Login in a secure browser (ASWebAuthenticationSession on iOS, Chrome Custom Tabs on Android). The `clearSession()` method logs the user out and clears both the browser session and stored credentials. The `customScheme` parameter must match the value in your `app.json` plugin configuration.
    </Info>
  </Step>

  <Step title="Run your app" stepNumber={6}>
    Build and run your Expo application on a device or emulator.

    **First, generate the native iOS and Android projects:**

    ```bash theme={null}
    npx expo prebuild
    ```

    **Then run on your target platform:**

    **For iOS:**

    ```bash theme={null}
    npx expo run:ios
    ```

    **For Android:**

    ```bash theme={null}
    npx expo run:android
    ```

    **Expected flow:**

    1. App launches showing "Log In" button
    2. Tap **Log In** → Browser opens with Auth0 Universal Login
    3. Complete login (sign up or sign in)
    4. Browser closes → Returns to app automatically
    5. User profile displays with name, email, and avatar

    <Tip>
      If you make changes to the plugin configuration in `app.json`, run `npx expo prebuild --clean` to regenerate the native projects with the updated configuration.
    </Tip>

    <Warning>
      iOS Simulator requires a valid Apple Developer account for ASWebAuthenticationSession. For testing on simulator without an account, use a physical device or Android emulator instead.
    </Warning>
  </Step>
</Steps>

<Check>
  **Checkpoint**

  You should now have a fully functional Auth0 login experience running on your device or emulator. The app uses secure browser authentication and automatically manages credentials in the device's secure storage.
</Check>

***

## Troubleshooting & Advanced

<Accordion title="Common Issues & Solutions">
  ### "Invariant Violation: Native module cannot be null"

  This error occurs when attempting to use the SDK with Expo Go.

  **Solution:**

  The Auth0 SDK requires custom native code that isn't available in Expo Go. Use a development build instead:

  ```bash theme={null}
  npx expo run:ios
  # or
  npx expo run:android
  ```

  ### Callback URL mismatch error

  **Solution:**

  Verify all three of these match exactly:

  1. `customScheme` in `app.json` plugin configuration
  2. `customScheme` parameter passed to `authorize()` and `clearSession()`
  3. Callback URLs in Auth0 Dashboard (Applications → Your App → Settings → Application URIs)

  ### "PKCE not allowed" error

  **Fix:**

  1. Go to Auth0 Dashboard → Applications → Your Application
  2. Change application type to **Native**
  3. Save changes and try again

  ### Prebuild fails or plugin not applied

  **Fix:**

  ```bash theme={null}
  # Clean and regenerate native projects
  npx expo prebuild --clean
  ```

  ### iOS build fails with Pod errors

  **Fix:**

  ```bash theme={null}
  cd ios
  pod install --repo-update
  cd ..
  npx expo run:ios
  ```

  ### User cancelled error

  Handle gracefully in your login function:

  ```jsx expandable theme={null}
  const handleLogin = async () => {
    try {
      await authorize({customScheme: 'auth0sample', scope: 'openid profile email'});
    } catch (e) {
      if (e.message === 'a0.session.user_cancelled') {
        // User closed login screen - handle gracefully
        console.log('Login cancelled by user');
      } else {
        console.error('Login failed:', e);
      }
    }
  };
  ```

  ### iOS Alert Dialog

  On iOS, users see a permission dialog: *"App Name" Wants to Use "auth0.com" to Sign In*. This is expected behavior from `ASWebAuthenticationSession`. Users must tap **Continue** to proceed.

  To customize this behavior, you can use ephemeral sessions (disables SSO):

  ```jsx theme={null}
  await authorize({customScheme: 'auth0sample', scope: 'openid profile email'}, {ephemeralSession: true});
  ```
</Accordion>

<Accordion title="Retrieving Access Tokens">
  Use the `getCredentials()` method to retrieve tokens for API calls:

  ```jsx expandable lines theme={null}
  import {useAuth0} from 'react-native-auth0';

  const MyComponent = () => {
    const {getCredentials} = useAuth0();

    const callApi = async () => {
      try {
        const credentials = await getCredentials();
        const response = await fetch('https://your-api.com/endpoint', {
          headers: {
            Authorization: `Bearer ${credentials.accessToken}`,
          },
        });
        // Handle response
      } catch (e) {
        console.error('Failed to get credentials', e);
      }
    };
  };
  ```

  <Tip>
    Include the `offline_access` scope during login to receive a refresh token: `authorize({customScheme: 'auth0sample', scope: 'openid profile email offline_access'})`. This enables automatic token renewal.
  </Tip>
</Accordion>

<Accordion title="Check Authentication Status on App Launch">
  Use `hasValidCredentials()` to check if the user is already logged in:

  ```jsx expandable lines theme={null}
  import {useAuth0} from 'react-native-auth0';
  import {useEffect} from 'react';

  const App = () => {
    const {hasValidCredentials, getCredentials} = useAuth0();

    useEffect(() => {
      const checkAuth = async () => {
        const isLoggedIn = await hasValidCredentials();
        if (isLoggedIn) {
          const credentials = await getCredentials();
          // User is authenticated, load their data
        }
      };
      checkAuth();
    }, []);
  };
  ```
</Accordion>

<Accordion title="Production Deployment with EAS Build">
  For production builds, use EAS Build instead of local development builds.

  **Install EAS CLI:**

  ```bash theme={null}
  npm install -g eas-cli
  ```

  **Create `eas.json` in your project root:**

  ```json eas.json lines theme={null}
  {
    "cli": {
      "version": ">= 3.0.0"
    },
    "build": {
      "development": {
        "developmentClient": true,
        "distribution": "internal"
      },
      "preview": {
        "distribution": "internal"
      },
      "production": {}
    }
  }
  ```

  **Build for production:**

  ```bash theme={null}
  # Build for both platforms
  eas build --platform all

  # Or build for specific platform
  eas build --platform ios
  eas build --platform android
  ```

  ### Before deploying to production

  **Use HTTPS callback URLs** for enhanced security:

  ```text theme={null}
  https://{yourDomain}/ios/{bundleId}/callback
  https://{yourDomain}/android/{packageName}/callback
  ```

  **Configure Android App Links** in Auth0 Dashboard:

  * Settings → Advanced Settings → Device Settings
  * Add your app's SHA-256 fingerprint

  **Configure iOS Universal Links:**

  * Add Associated Domains capability in Xcode
  * Add `webcredentials:{yourDomain}` to Associated Domains

  **Review security settings** in Auth0 Dashboard:

  * Enable **OIDC Conformant** in Advanced Settings
  * Configure **Token Expiration** appropriately
  * Set up **Brute Force Protection**
  * Test on multiple devices and OS versions
  * Implement proper error handling for network failures

  <Tip>
    For production apps, consider using HTTPS callback URLs with Universal Links (iOS) and App Links (Android) instead of custom schemes for enhanced security.
  </Tip>
</Accordion>

* [Sample Application](https://github.com/auth0-samples/auth0-react-native-sample/tree/master/00-Login-Expo)
* [Migration Guide (v4 to v5)](https://github.com/auth0/react-native-auth0/blob/master/MIGRATION_GUIDE.md)
