> For the complete documentation index, see [llms.txt](https://fusionauth.io/docs/llms.txt)

# React Native | FusionAuth Docs

Quickstart integration of React Native application with FusionAuth.

# React Native

[Edit on GitHub](https://github.com/FusionAuth/fusionauth-site/blob/main/astro/src/content/docs/get-started/quickstarts/app/quickstart-react-native.mdx)

[View Markdown](https://fusionauth.io/docs/get-started/quickstarts/app/quickstart-react-native.md)

In this quickstart, you are going to build an application with React Native and integrate it with FusionAuth. You'll be building it for [ChangeBank](https://www.youtube.com/watch?v=CXDxNCzUspM), a global leader in converting dollars into coins. It'll have areas reserved for users who have logged in as well as public facing sections.

The Docker Compose file and source code for a complete application are available at [https://github.com/FusionAuth/fusionauth-quickstart-react-native](https://github.com/FusionAuth/fusionauth-quickstart-react-native).

## Prerequisites[#](#prerequisites)

*   [Node](https://nodejs.org): This will be used to set up your project.
*   [Docker](https://www.docker.com): The quickest way to stand up FusionAuth. (There are [other ways](https://fusionauth.io/docs/get-started/download-and-install.md)).
*   To test on Android devices, you can either connect a physical device or [Android Studio](https://developer.android.com/studio) to set up an emulator.
*   To test on iOS devices, you'll need a Mac and install [Xcode](https://developer.apple.com/xcode/) to set up an emulator.

This app has been tested with React Native 0.72.4 and Node 20. This example should work with other compatible versions of React Native.

## General Architecture[#](#general-architecture)

Here's a typical application login flow before FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application

    User ->> App : View Homepage
    User ->> App : Click Login Link
    App ->> User : Show Login Form
    User ->> App : Fill Out and Submit Login Form
    App ->> App : Authenticates User
    App ->> User : Display User's Account or Other Info
```

Request flow during login before FusionAuth

And here's the same application login flow after introducing FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application
    participant FusionAuth

    User ->> App : View Homepage
    User ->> App : Click Login Link (to FusionAuth)
    User ->> FusionAuth : View Login Form
    FusionAuth ->> User : Show Login Form
    User ->> FusionAuth : Fill Out and Submit Login Form
    FusionAuth ->> FusionAuth : Authenticates User
    FusionAuth ->> User: Go to Redirect URI
    User ->> App: Request the Redirect URI
    App ->> FusionAuth : Is User Authenticated?
    FusionAuth ->> App : User is Authenticated
    App ->> User : Display User's Account or Other Info
```

Request flow during login after FusionAuth

In general, you are introducing FusionAuth in order to normalize and consolidate user data. This helps make sure it is consistent and up-to-date as well as offloading your login security and functionality to FusionAuth.

## Getting Started[#](#getting-started)

In this section, you'll get FusionAuth up and running and use `npx` to create a new application.

### Clone the Code[#](#clone-the-code)

First off, grab the code from the repository and change into that directory.

```plaintext
git clone https://github.com/FusionAuth/fusionauth-quickstart-react-native.git
cd fusionauth-quickstart-react-native
```

### Run FusionAuth via Docker[#](#run-fusionauth-via-docker)

You'll find a Docker Compose file (`docker-compose.yml`) and an environment variables configuration file (`.env`) in the root directory of the repo.

Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.

```shell-session
docker compose up -d
```

Here you are using a bootstrapping feature of FusionAuth called [Kickstart](https://fusionauth.io/docs/get-started/download-and-install/development/kickstart.md). When FusionAuth comes up for the first time, it will look at the `kickstart/kickstart.json` file and configure FusionAuth to your specified state.

If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing `docker compose down -v`, then re-run `docker compose up -d`.

FusionAuth will be initially configured with these settings:

*   Your client Id is `E9FDB985-9173-4E01-9D73-AC2D60D1DC8E`.
*   Your client secret is `super-secret-secret-that-should-be-regenerated-for-production`.
*   Your example username is `richard@example.com` and the password is `password`.
*   Your admin username is `admin@example.com` and the password is `password`.
*   The base URL of FusionAuth is `http://localhost:9011/`.

You can log in to the [FusionAuth admin UI](http://localhost:9011/admin) and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

If you want to see where the FusionAuth values came from, they can be found in the [FusionAuth app](http://localhost:9011/admin). The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the `View` icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the `OAuth configuration` section.

The `.env` file contains passwords. In a real application, always add this file to your `.gitignore` file and never commit secrets to version control.

### Set Up A Public URL For FusionAuth[#](#set-up-a-public-url-for-fusionauth)

Your FusionAuth instance is now running on a different machine (your computer) than the mobile app will run (either a real device or an emulator), which means that it won't be able to access `localhost`.

If the device and your computer are not connected to the same network or if you have something that blocks connections (like a firewall), learn [how to expose a local FusionAuth instance to the internet](https://fusionauth.io/docs/get-started/download-and-install/development/exposing-instance.md). In summary, the process entails configuring [ngrok](https://ngrok.com/download) on your local system, [starting your FusionAuth instance on port 9011](#run-fusionauth-via-docker), and subsequently executing the following command.

```shell
ngrok http --request-header-add 'X-Forwarded-Port:443' 9011
```

This will generate a public URL that you can use to access FusionAuth when developing the app.

If the device (either real or emulator) and your computer are connected to the same network, you can use the local IP Address for your machine (for example, `192.168.15.2`). Here are a few articles to help you find your IP address, depending on the operating system you are running:

*   [Linux](https://linuxize.com/post/how-to-find-ip-address-linux/#find-your-public-ip-address)
*   [Mac](https://support.apple.com/guide/mac-help/find-your-computers-name-and-network-address-mchlp1177/mac)
*   [Windows](https://support.microsoft.com/en-us/windows/find-your-ip-address-in-windows-f21a9bbc-c582-55cd-35e0-73431160a1b9)

### Create your React Native Application[#](#create-your-react-native-application)

Now you're going to create a React Native application. While this section builds a simple React Native application, you can use the same configuration to integrate your existing React Native application with FusionAuth. To make things easier, you're going to use `create-expo-app`, a library that sets up the environment using [Expo](https://docs.expo.dev/), a platform that runs natively on all your users' devices

```shell
npx create-expo-app my-react-native-app && cd my-react-native-app
```

You'll have to create all files in the root directory for your application.

If this is your first time setting up a React Native application, you'll receive a message asking if you want to install `create-expo-app`, so press `y` to confirm.

## Authentication[#](#authentication)

We'll use the [Expo AuthSession library](https://docs.expo.dev/versions/latest/sdk/auth-session/), which simplifies integrating with FusionAuth and creating a secure web application.

### Configure Expo AuthSession[#](#configure-expo-authsession)

Install `expo-auth-session`, its dependency `expo-crypto` to handle cryptographic operations and `expo-web-browser` to interact with the device Browser in order to log the user in or out.

```shell
npx expo install expo-auth-session expo-crypto expo-web-browser
```

Create a `.env` file to hold information about your FusionAuth instance and application, replacing the value in **`EXPO_PUBLIC_FUSIONAUTH_URL`** with the address you copied from `ngrok` when [exposing your instance](#set-up-a-public-url-for-fusionauth).

```ini
EXPO_PUBLIC_FUSIONAUTH_CLIENT_ID=e9fdb985-9173-4e01-9d73-ac2d60d1dc8e
EXPO_PUBLIC_FUSIONAUTH_ISSUER=acme.com
EXPO_PUBLIC_FUSIONAUTH_URL=Change this value to the address ngrok gave you
```

Replace `app.json` with the contents below to add some details about your app:

*   A `scheme` that will be used when redirecting back to your application after logging in and out of FusionAuth.
*   The package name for both Android (`expo.android.package`) and iOS (`expo.ios.bundleIdentifier`) builds.

```json
{
  "expo": {
    "name": "my-react-native-app",
    "slug": "my-react-native-app",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "io.fusionauth.app"
    },
    "android": {
      "package": "io.fusionauth.app",
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      }
    },
    "web": {
      "favicon": "./assets/favicon.png"
    },
    "scheme": "io.fusionauth.app"
  }
}
```

## App Customization[#](#app-customization)

In this section, you'll turn your application into a trivial banking application with some styling.

### Add Styling[#](#add-styling)

First, run the command below to install some libraries needed for theming.

```shell
npm install expo-image expo-constants react-native-currency-input
```

Instead of using CSS, React Native has its own concept of [stylesheets](https://reactnative.dev/docs/stylesheet). Create a file named `changebank.style.js` with the contents below to style your ChangeBank app.

```javascript
import {StyleSheet} from 'react-native';

export default StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
    },
    h1: {
        color: '#096324',
        fontSize: 30,
        fontWeight: '600',
    },
    h3: {
        color: '#096324',
        marginTop: 20,
        marginBottom: 40,
        fontSize: 24,
        fontWeight: '600',
    },
    a: {
        color: '#096324',
    },
    p: {
        fontSize: 18,
    },
    headerEmail: {
        color: '#096324',
    },
    finePrint: {
        fontSize: 16,
    },
    body: {
        fontFamily: 'sans-serif',
        padding: 0,
        margin: 0,
    },
    hRow: {
        display: 'flex',
        alignItems: 'flex-end',
        flex: 1,
        rowGap: 10,
    },
    pageHeader: {
        display: 'flex',
        flexDirection: 'column',
        width: '100%',
    },
    logoHeader: {
        display: 'flex',
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center',
        height: 150,
        paddingVertical: 10,
        marginHorizontal: 20,
    },
    menuBar: {
        display: 'flex',
        flexDirection: 'row',
        columnGap: 10,
        alignItems: 'center',
        paddingVertical: 15,
        paddingHorizontal: 20,
        backgroundColor: '#096324',
    },
    menuLink: {
        fontWeight: '600',
        color: '#ffffff',
    },
    buttonLg: {
        backgroundColor: '#096324',
        color: '#ffffff',
        fontSize: 16,
        fontWeight: '700',
        borderRadius: 10,
        textAlign: 'center',
        paddingHorizontal: 15,
        paddingVertical: 5,
        textDecorationLine: 'none',
        overflow: 'hidden',
    },
    contentContainer: {
        flex: 1,
        display: 'flex',
        flexDirection: 'column',
        paddingTop: 60,
        paddingRight: 20,
        paddingBottom: 20,
        paddingLeft: 40,
    },
    balance: {
        fontSize: 50,
        fontWeight: '800',
    },
    changeLabel: {
        flex: 1,
        fontSize: 20,
        marginRight: 5,
    },
    changeInput: {
        flex: 1,
        flexGrow: 1,
        flexShrink: 0,
        fontSize: 20,
        borderColor: '#999',
        borderWidth: 1,
        padding: 5,
        textAlign: 'right',
    },
    changeMessage: {
        fontSize: 20,
        marginBottom: 15,
    },
    appContainer: {
        width: '100%',
        marginTop: 40,
        paddingHorizontal: 20,
    },
    changeContainer: {
        flex: 1,
    },
    image: {
        flex: 1,
        height: '100%',
    },
    inputContainer: {
        display: 'flex',
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: "space-between",
        marginVertical: 10,
    },
});
```

Run the command below to download the ChangeBank logo into the `assets` folder.

```shell
wget -O assets/changebank.svg https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-react-native/main/complete-application/assets/changebank.svg
```

## Finish Setting up the App[#](#finish-setting-up-the-app)

Replace the existing `App.js` to integrate `expo-auth-session`, add the ChangeBank template, and stitch everything up.

```javascript
import {useEffect, useState} from 'react';
import Constants from 'expo-constants';
import CurrencyInput from 'react-native-currency-input';
import styles from './changebank.style';
import {openAuthSessionAsync} from 'expo-web-browser';
import {Text, TouchableOpacity, View} from 'react-native';
import {
  exchangeCodeAsync,
  fetchUserInfoAsync,
  makeRedirectUri,
  useAuthRequest,
  useAutoDiscovery,
  AuthRequest,
  AuthSessionResult
} from 'expo-auth-session';
import {Image} from 'expo-image';
import {StatusBar} from 'expo-status-bar';

export default function App() {
  /**
   * This will hold the access token and the user details after successful authorization
   */
  const [authResponse, setAuthResponse] = useState(null);

  /**
   * This is what the ChangeBank app will use to make change
   */
  const [amount, setAmount] = useState(0);

  /**
   * This is a helper function from expo-auth-session to retrieve the URLs used for authorization
   */
  const discovery = useAutoDiscovery(process.env.EXPO_PUBLIC_FUSIONAUTH_URL);

  /**
   * Creating a new Redirect URI using the scheme configured in app.json.
   * Expo Go will override this with a local URL when developing.
   */
  const redirectUri = makeRedirectUri({
    scheme: Constants.expoConfig.scheme,
    path: 'redirect',
  });

  /**
   * useAuthRequest() is another helper function from expo-auth-session that handles the authorization request.
   * It returns a promptLogin() function that should be called to initiate the process.
   */
  const [requestLogin, responseLogin, promptLogin] = useAuthRequest({
    clientId: process.env.EXPO_PUBLIC_FUSIONAUTH_CLIENT_ID,
    scopes: ['openid', 'offline_access'],
    usePKCE: true,
    redirectUri,
  }, discovery);

  /**
   * We do the same thing as above but for the user registration endpoint.
   */
  const [requestRegister, responseRegister, promptRegister] = useAuthRequest({
    clientId: process.env.EXPO_PUBLIC_FUSIONAUTH_CLIENT_ID,
    scopes: ['openid', 'offline_access'],
    usePKCE: true,
    redirectUri,
  }, (discovery) ? {
    ...discovery,
    authorizationEndpoint: discovery.authorizationEndpoint.replace('/authorize', '/register')
  } : null);

  /**
   * To log the user out, we redirect to the end session endpoint
   *
   * @return {void}
   */
  const logout = () => {
    const params = new URLSearchParams({
      client_id: process.env.EXPO_PUBLIC_FUSIONAUTH_CLIENT_ID,
      post_logout_redirect_uri: redirectUri,
    });
    openAuthSessionAsync(discovery.endSessionEndpoint + '?' + params.toString(), redirectUri)
      .then((result) => {
        if (result.type !== 'success') {
          handleError(new Error('Please, confirm the logout request and wait for it to finish.'));
          console.error(result);
          return;
        }
        setAuthResponse(null);
      });
  };

  /**
   * Auxiliary function to handle displaying errors
   *
   * @param {Error} error
   */
  const handleError = (error) => {
    console.error(error);
    alert(error.message);
  };

  /**
   * This will handle login and register operations
   *
   * @param {AuthRequest} request
   * @param {AuthSessionResult} response
   */
  const handleOperation = (request, response) => {
    if (!response) {
      return;
    }

    /**
     * If something wrong happened, we call our error helper function
     */
    if (response.type !== 'success') {
      handleError(response.error || new Error(`Operation failed: ${response.type}`));
      return;
    }

    /**
     * If the authorization process worked, we need to exchange the authorization code for an access token.
     */
    exchangeCodeAsync({
      clientId: process.env.EXPO_PUBLIC_FUSIONAUTH_CLIENT_ID,
      code: response.params.code,
      extraParams: {
        code_verifier: request.codeVerifier,
      },
      redirectUri,
    }, discovery).then((response) => {
      // Now that we have an access token, we can call the /oauth2/userinfo endpoint
      fetchUserInfoAsync(response, discovery).then((userRecord) => setAuthResponse({
        accessToken: response.accessToken,
        user: userRecord,
      })).catch(handleError);
    }).catch(handleError);
  };

  /*
   * This is a React Hook that will call the handleOperation() method
   * whenever the login process redirects from the browser to our app.
   */
  useEffect(() => {
    handleOperation(requestLogin, responseLogin);
  }, [responseLogin]);

  /*
   * This is a React Hook that will call the handleOperation() method
   * whenever the signup process redirects from the browser to our app.
   */
  useEffect(() => {
    handleOperation(requestRegister, responseRegister);
  }, [responseRegister]);

  /**
   * Making change for our ChangeBank app
   */
  const amountCents = amount * 100;
  const nickels = Math.floor(amountCents / 5);

  return (
      <View style={styles.container}>
        <StatusBar style="auto"/>
        <View style={[styles.pageHeader, {marginTop: Constants.statusBarHeight}]}>
          <View style={styles.logoHeader}>
            <Image
                source={require('./assets/changebank.svg')}
                style={styles.image}
                contentFit="contain"
                transition={1000}
            />
            <View style={styles.hRow}>
              {(authResponse) ? (
                  <>
                    <Text style={styles.headerEmail}>{authResponse.user.email}</Text>
                    <TouchableOpacity disabled={!requestLogin} onPress={() => logout()}>
                      <Text style={styles.buttonLg}>Log out</Text>
                    </TouchableOpacity>
                  </>
              ) : (
                  <>
                    <TouchableOpacity disabled={!requestLogin} onPress={() => promptLogin()}>
                      <Text style={styles.buttonLg}>Log in</Text>
                    </TouchableOpacity>
                    <TouchableOpacity disabled={!requestLogin} onPress={() => promptRegister()}>
                      <Text style={styles.buttonLg}>Register</Text>
                    </TouchableOpacity>
                  </>
              )}
            </View>
          </View>

          <View style={styles.menuBar}>
            <Text style={styles.menuLink}>{(authResponse) ? 'Make Change' : 'Home'}</Text>
          </View>
        </View>

        <View style={styles.appContainer}>
          {(authResponse) ? (
              <>
                <Text style={styles.h1}>We Make Change</Text>
                <View style={styles.inputContainer}>
                  <Text style={styles.changeLabel}>Amount in USD:</Text>
                  <CurrencyInput
                      prefix="$ "
                      delimiter=","
                      separator="."
                      value={amount}
                      onChangeValue={setAmount}
                      style={styles.changeInput}
                  />
                </View>
                <Text style={styles.changeMessage}>
                  We can make change for ${(amount || 0).toFixed(2)} with {nickels} nickels and{' '}
                  {Math.round(amountCents % 5)} pennies!
                </Text>
              </>
          ) : (
              <Text style={styles.h1}>Log in to manage your account</Text>
          )}
        </View>
      </View>
  );
}
```

## Run the App[#](#run-the-app)

Depending on where you want to test your app, follow these instructions.

*   For Android devices: you can either [connect a physical device](https://developer.android.com/studio/run/device) or install [Android Studio](https://developer.android.com/studio) to [set up an emulator](https://developer.android.com/studio/run/emulator).
*   For iOS devices: if you are running a Mac, you can install [Xcode](https://developer.apple.com/xcode/) to [set up an emulator or run on a physical device](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device/).

Now that you have a device connected or an emulator running, start up the React Native application from the root `my-react-native-app` directory using the command below.

```shell
npx expo start
```

After waiting a few moments, you should see a QR Code and a menu with some actions. Right below the QR Code, you'll see a message like this one *(the real address may vary)*.

```plaintext
› Metro waiting on exp://192.168.1.2:8081
```

To use [Expo Go](https://docs.expo.dev/get-started/expo-go/), a client for testing your apps on Android and iOS devices without building anything locally, you need to:

*   Copy that listening address (`exp://192.168.1.2:8081`).
*   Navigate to your FusionAuth instance on [localhost:9011](http://localhost:9011).
*   Log in with the email `admin@example.com` and password `password`.
*   Navigate to Applications .
*   Click on in your `ExampleReactNativeApp` to edit it.
*   Go to the OAuth tab.
*   Add that address to **Authorized redirect URLs** and append `/--/redirect` to it
    *   Example: for `exp://192.168.1.2:8081`, you'd need to add `exp://192.168.1.2:8081/--/redirect` as a redirect URL
*   Click on to save your changes.

Go back to the terminal with the Expo menu. To test on the connected or emulated Android device, press `a`. Otherwise, press `i` to run on the iOS device.

Wait a few seconds and Expo will build and install the app in your device. You should then see the ChangeBank welcome page. Click the Log in button in the top right corner of that screen.

If it is your first time running the app, ngrok will ask if you really want to continue to that page, so click Visit Site .

You'll finally arrive at the FusionAuth login screen. Fill in `richard@example.com` and `password` and click on Submit to be redirected back to the logged-in ChangeBank page.

## Made it this far? Want a free t-shirt? We got ya.

Thank you for spending some time getting familiar with FusionAuth.

\*Offer only valid in the United States and Canada, while supplies last.

## Next Steps[#](#next-steps)

This quickstart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you're going to want to do.

### FusionAuth Customization

FusionAuth gives you the ability to customize just about everything to do with the user's experience and the integration of your application. This includes:

*   [Hosted pages](https://fusionauth.io/docs/customize/look-and-feel.md) such as login, registration, email verification, and many more.
*   [Email templates](https://fusionauth.io/docs/customize/email-and-messages/email-templates.md).
*   [User data and custom claims in access token JWTs](https://fusionauth.io/articles/tokens/jwt-components-explained.md).

### Security

*   [Expo AuthSession](https://docs.expo.dev/versions/latest/sdk/auth-session/) handles token validation and refresh.
*   You may want to customize the [token expiration times and policies](https://fusionauth.io/docs/lifecycle/authenticate-users/oauth.md) in FusionAuth.
*   Choose [password rules](https://fusionauth.io/docs/get-started/core-concepts/tenants.md#password) and a [hashing algorithm](https://fusionauth.io/docs/reference/password-hashes.md) that meet your security needs.

### Tenant and Application Management

*   Model your application topology using [Applications](https://fusionauth.io/docs/get-started/core-concepts/applications.md), [Roles](https://fusionauth.io/docs/get-started/core-concepts/roles.md), [Groups](https://fusionauth.io/docs/get-started/core-concepts/groups.md), [Entities](https://fusionauth.io/docs/get-started/core-concepts/groups.md), and more.
*   Set up [MFA](https://fusionauth.io/docs/lifecycle/authenticate-users/multi-factor-authentication.md), [Social login](https://fusionauth.io/docs/lifecycle/authenticate-users/identity-providers.md), or [SAML](https://fusionauth.io/docs/lifecycle/authenticate-users/identity-providers/overview-samlv2.md) integrations.
*   Integrate with external systems using [Webhooks](https://fusionauth.io/docs/extend/events-and-webhooks.md), [SCIM](https://fusionauth.io/docs/lifecycle/migrate-users/scim.md), and [Lambdas](https://fusionauth.io/docs/extend/code/lambdas.md).

### Build your App for Distribution[#](#build-your-app-for-distribution)

Follow [Expo's "Create your first build" tutorial](https://docs.expo.dev/build/setup/) to learn how to create a build for your app.

## Troubleshooting[#](#troubleshooting)

*   I keep receiving an `invalid_redirect_uri` when running the app with Expo Go

Make sure you have updated the **Authorized redirect URLs** in your [FusionAuth instance](http://localhost:9011) like shown on the [Run the App](#run-the-app) step.