Email Authentication with Firebase in React Native for iOS and Android
Published: 2023-12-13 16:13:23

This post aims to provide a comprehensive guide on implementing email authentication in a React Native application using Firebase. As of December 2023, I leverage some of the latest packages and methodologies to build a secure and efficient authentication system. My main goal is to practice writing clear and effective instructional guides, which others and I can use later. The article was written with the help of existing documentation, AI tools like ChatGPT and Grammarly - I’m not sure if it uses AI yet, probably yes - and other human beings.

## Table of content

1. [Prerequisites](#prerequisites)
2. [Development Stack](#development-stack)
3. [Setting Up the React Native Environment](#setting-up-the-react-native-environment)
4. [Setting Up Firebase for Email Authentication](#setting-up-firebase-for-email-authentication)
5. [Structuring the React Native Project](#structuring-the-react-native-project)
6. [Signup](#signup)
7. [Logout](#logout)
8. [Login](#login)
9. [Conditional Navigation Based on Authentication State](#conditional-navigation-based-on-authentication-state)
10. [Additional Features and Improvements](#additional-features-and-improvements)
11. [Sources](#sources)

### Prerequisites
To get the most out of this tutorial, you should have a fundamental understanding of React and React Native, familiarity with npm for package management, and experience with Git for version control. The guide assumes a working knowledge of these technologies, allowing us to focus on the specific task of integrating Firebase for authentication purposes.

### Development Stack

- npm: 10.1.
- Node.js: v20.9.0
- Watchman: 2023.10.30.00
- Visual Studio Code (VSCode): 1.85.0
- Xcode: 14.2
- Android Studio: Android Studio Giraffe | 2022.3.1 
- Java: Version 17
- React Native: 0.73.0
- React Native CLI: removed as the official react-native doc suggests it
- Firebase: 10.7.1

### Setting Up the React Native Environment
You can follow the instructions specific to your development OS and target OS: [RN documentation](https://reactnative.dev/docs/environment-setup?guide=native&platform=ios)

Make sure you carefully follow the guide for both `iOS` and `Android`. Make sure you have installed the correct version of npm, node, Xcode, android studio, android SDKs, java, etc.

#### Initializing the React Native Project
To create a new project, run:

`npx react-native@latest init CodePlayground`

After successful initialization, you should see a series of steps being completed, including template downloading, dependency installation, and potentially CocoaPods setup for iOS.
####  Testing the Setup
It's a good practice to test the setup on both iOS and Android platforms. Navigate to your project directory:

`cd CodePlayground`

Run the default app on both simulators to confirm everything is set up correctly:

`npm start`

Then, press `i` to run on iOS and `a` to run on Android.
Note that sometimes an initial build might fail, particularly if specific SDKs or configurations are missing. In such cases, open Xcode for iOS and Android Studio for Android to build the app once and ensure all necessary components are installed.
After making sure everything works properly so far, use your favorite version control like git to keep track of changes. _(Optional)_

### Setting Up Firebase for Email Authentication

1. **Start a New Project**: Navigate to the [Firebase Console](https://console.firebase.google.com/) and start a new project. Enter a project name and accept the default settings.
![New project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qyh5xfsrlfr8s7omhjyu.png)
![Add name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbikgr9671sahx38cwyj.png)
![Default settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/032isfhga73mibgffk0l.png)
![Account selection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3ywgdtaxckgjlhn2hj5.png)
2. **Project Creation**: While Firebase prepares your project, take a brief coffee break! Your new Firebase environment will be ready shortly.
![Project creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gm1w9o59no92n4kj2m0m.png)
3. **Enable Email/Password Sign-In Method**: In your Firebase project, go to the Authentication section and enable the 'Email/Password' sign-in provider. This step is crucial for email-based user authentication.
![Email authentication select](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxy35gxyw60t9iao39kz.png)
![Email auth get started](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shca3xof663itchc4f9f.png)
![Select email auth](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3z5u9icyjn30okzn103h.png)
![Enable email auth](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idizultbop6l47ck1qmt.png)
![Email auth enabled](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6qy4p6vxods90s2u94pn.png)
4. **Register a Web App**: To obtain the necessary credentials, add a web application to your Firebase project. Provide a nickname for your app and follow the setup instructions.
![Add web app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xvz96uyyhylheezdkq6.png)
![Add name to webapp](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ftbtyf0lsxihweqomqcc.png)
5. **Install Firebase in Your Project**: Run `npm install firebase` in your project directory to add Firebase SDK.![Api keys](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/268r9469g5yh51vvva41.png)
6. **Initialize Firebase with Configuration**: Create a `firebase.js` file in your project root. Add the following code with your specific configuration
 ```
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "A….”,
  authDomain: "cod….”,
  projectId: "cod…”,
  storageBucket: "cod…”,
  messagingSenderId: "47…”,
  appId: "1:475…”,
  measurementId: “G…”
}

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
```
7.**Use Environment Variables**: For security, store your Firebase configuration in a `.env` file at the root of your project. Add this file to `.gitignore` to keep your `keys` safe and out of version control.

Add to your `.gitignore`
```
# Environment variables
.env
```

Create `.env` in your root

```
API_KEY=A…8
AUTH_DOMAIN=codep…app.com
PROJECT_ID=codep…a
STORAGE_BUCKET=ycode…com
MESSAGING_SENDER_ID=4…2
APP_ID=1:47…f
MEASUREMENT_ID=G…G
```
8.**Install react-native-dotenv**: Use `npm install react-native-dotenv` for safe access to environment variables.
9.**Update Babel Configuration**: Modify `babel.config.js` to include `react-native-dotenv`:
```
module.exports = { presets: ['module:@react-native/babel-preset'], plugins: [ ['module:react-native-dotenv', { moduleName: '@env', path: '.env', }], ], };
```
10.**Update firabse.js file**: where you are supposed to copy-paste the configs provided by Firebase and make the following changes
```
const firebaseConfig = {
  apiKey: process.env.API_KEY,
  authDomain: process.env.AUTH_DOMAIN,
  projectId: process.env.PROJECT_ID,
  storageBucket: process.env.STORAGE_BUCKET,
  messagingSenderId: process.env.MESSAGING_SENDER_ID,
  appId: process.env.APP_ID,
  measurementId: process.env.MEASUREMENT_ID
};
```
Also, add `import { getAuth } from 'firebase/auth';` at the imports of your file and add `export const auth = getAuth(app);` to the end of the file


You can comment out or delete the analytics for now and the `firebase.js` in your root should look like this:


```
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// import { getAnalytics } from "firebase/analytics";
import { getAuth } from 'firebase/auth';

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: process.env.API_KEY,
  authDomain: process.env.AUTH_DOMAIN,
  projectId: process.env.PROJECT_ID,
  storageBucket: process.env.STORAGE_BUCKET,
  messagingSenderId: process.env.MESSAGING_SENDER_ID,
  appId: process.env.APP_ID,
  measurementId: process.env.MEASUREMENT_ID
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
// const analytics = getAnalytics(app);
export const auth = getAuth(app);
```

## Structuring the React Native Project
Clean up the default react native project and add simple context, src folder, styles, and screens (Login, Signup, and Home)
I added simple stack navigation to the app for screen changing.

```
npm install @react-navigation/native
npm install react-native-screens react-native-safe-area-context
npm install @react-navigation/native-stack
```

```
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import Login from './src/screens/Login';
import Signup from './src/screens/Signup';
import Home from './src/screens/Home';

const Stack = createNativeStackNavigator();

function App() {
  return (
    <SafeAreaProvider>
      <SafeAreaView style={{ flex: 1 }}>
        <NavigationContainer>
          <Stack.Navigator initialRouteName="Login">
            <Stack.Screen name="Login" component={Login} />
            <Stack.Screen name="Signup" component={Signup} />
            <Stack.Screen name="Home" component={Home} />
          </Stack.Navigator>
        </NavigationContainer>
      </SafeAreaView>
    </SafeAreaProvider>
  );
}

export default App;
```


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

export const GlobalStyles = StyleSheet.create({
  screenContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    margin: 12,
  },
  inputField: {
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    width: '100%',
    marginBottom: 15,
    padding: 10,
  },
  button: {
    backgroundColor: 'blue',
    padding: 10,
    borderRadius: 5,
    marginBottom: 15,
  },
  buttonText: {
    color: 'white',
    textAlign: 'center',
  },
  linkText: {
    color: 'blue',
    textAlign: 'center',
    marginTop: 15,
  },
});
```

```
import React from 'react';
import { View, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { GlobalStyles } from '../styles/GlobalStyles';

export default function Login() {
    const navigation = useNavigation();

    const handleSignUpPress = () => {
      navigation.navigate('Signup');
    };
  
    const handleLoginPress = () => {
      // TODO:Perform authentication...
      navigation.navigate('Home');
    };

    return (
        <View style={GlobalStyles.screenContainer}>
        <TextInput style={GlobalStyles.inputField} placeholder="Email" />
        <TextInput style={GlobalStyles.inputField} placeholder="Password" secureTextEntry />
        <TouchableOpacity style={GlobalStyles.button} onPress={handleLoginPress}>
            <Text style={GlobalStyles.buttonText}>Log In</Text>
        </TouchableOpacity>
        <Text style={GlobalStyles.linkText} onPress={handleSignUpPress}>Sign Up</Text>
        </View>
    );
}
```
![Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6d1ik9iczz2b1wt911zr.png)

```
import React from 'react';
import { Text, TextInput, TouchableOpacity, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { GlobalStyles } from '../styles/GlobalStyles';

export default function Signup() {  
    const navigation = useNavigation();

    const handleCancelPress = () => {
      navigation.goBack();
    };

    return (
        <AreaView style={GlobalStyles.screenContainer}>
        <TextInput style={GlobalStyles.inputField} placeholder="Email" />
        <TextInput style={GlobalStyles.inputField} placeholder="Password" secureTextEntry />
        <TextInput style={GlobalStyles.inputField} placeholder="Re-enter Password" secureTextEntry />
        <TouchableOpacity style={GlobalStyles.button}>
            <Text style={GlobalStyles.buttonText}>Sign Up</Text>
        </TouchableOpacity>
        <Text style={GlobalStyles.linkText} onPress={handleCancelPress}>Cancel</Text>
        </AreaView>
    );
}
```

![Signup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhvdsvvk5oy3rrvejxr3.png)

```
import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { GlobalStyles } from '../styles/GlobalStyles';

export default function Home() {
  return (
    <View style={GlobalStyles.screenContainer}>
      <Text>Welcome to the Home Screen!</Text>
      <TouchableOpacity style={GlobalStyles.button}>
        <Text style={GlobalStyles.buttonText}>Sign Out</Text>
      </TouchableOpacity>
    </View>
  );
}
```

![Home](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ven18ngmdjfiufia5ioz.png)

## Signup
Add signup `form validation` and firebase `signup` function to `Signup.js`.

`npm i formik yup`

New imports:

```
import { Formik } from 'formik';
import * as yup from 'yup'; 
import { auth } from '../../firebase';
import { createUserWithEmailAndPassword  } from 'firebase/auth';
```

Implement `signup` in the `handleSignup` function and add it to the `onSubmit` property of the form.

```
const handleSignup = async (values, actions) => {
        if (values.password !== values.confirmPassword) {
            actions.setFieldError('confirmPassword', "Passwords don't match");
            return;
        }
        createUserWithEmailAndPassword(auth, values.email, values.password)
        .then((userCredential) => {
            console.log('Signed up:', userCredential.user);
            navigation.navigate('Home');
        })
        .catch((error) => {
            Alert.alert('Signup Failed', error.message);
        });
    };
```

For the full document please check [Signup.js](https://github.com/Balogh08/CodePlayground/blob/email_auth_setup/src/screens/Signup.js)

Try out, and sign up as a user. Check the Firebase authentication users tab. There is the user!!!

![Signed up user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/golcs200ctpgunvj1ek8.png)

## Logout
As the handleSignup function navigates to the Home screen on the happy path, first I implement the logout function, and as there is an authenticated user its email address can be displayed to make sure which user is logged in.

```
import React, { useState, useEffect } from 'react';
import { SafeAreaView, Text, TouchableOpacity, Alert } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { GlobalStyles } from '../styles/GlobalStyles';
import { auth } from '../../firebase';
import { signOut } from 'firebase/auth';

export default function Home() {
    const [userEmail, setUserEmail] = useState('');
    const navigation = useNavigation();

    //set useremail to present
    useEffect(() => {
      const user = auth.currentUser;
      if (user) {
          setUserEmail(user.email);
      }
  }, []);

    // logout the user
    const handleLogout = () => {
      signOut(auth).then(() => {
        navigation.navigate('Login');
      }).catch((error) => {
        Alert.alert('Logout Failed', error.message);
      });
    };
    return (
      <SafeAreaView style={GlobalStyles.screenContainer}>
        <Text>Welcome to the Home Screen, {userEmail}!</Text>
        <TouchableOpacity style={GlobalStyles.button} onPress={handleLogout}>
          <Text style={GlobalStyles.buttonText}>Sign Out</Text>
        </TouchableOpacity>
      </SafeAreaView>
    );
}
```

After clicking the Signout button the user is signed out and the login screen becomes visible.

## Login
For the login, implement similar form validation and submission, and on submit utilize the `login` function from firebase.

Add new imports

```
import { Formik } from 'formik';
import * as yup from 'yup';
import { auth } from '../../firebase';
import { signInWithEmailAndPassword } from 'firebase/auth';
```

Add the login implementation to `handleLogin` 

```
const handleLoginPress = (values) => {
        signInWithEmailAndPassword(auth, values.email, values.password)
            .then((userCredential) => {
                console.log('Logged in:', userCredential.user);
                navigation.navigate('Home');
            })
            .catch((error) => {
                Alert.alert('Authentication Failed', error.message);
            });
    };
```

If the credentials are correct the user should be logged in to the home screen after pressing the login button.

For the full document please check [Login.js](https://github.com/Balogh08/CodePlayground/blob/email_auth_setup/src/screens/Login.js)

## Conditional Navigation Based on Authentication State
One more thing to add. Home screen should be available only to authenticated users and the back to `login` button from the Home.js top left corner should be removed. Not authenticated users should only see login and signup screens.

For this 2 `Stack.Navigator` and `auth.onAuthStateChanged` functions can be utilized.

```
import React, { useState, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { auth } from './firebase';
import Login from './src/screens/Login';
import Signup from './src/screens/Signup';
import Home from './src/screens/Home';

const Stack = createNativeStackNavigator();

function App() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  // Check if user is authenticated
  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(user => {
      setIsAuthenticated(!!user);
    });

    return () => unsubscribe();
  }, []);

  return (
      <SafeAreaProvider>
        <SafeAreaView style={{ flex: 1 }}>
          <NavigationContainer>
            {isAuthenticated ? (
              <Stack.Navigator>
                <Stack.Screen name="Home" component={Home} />
              </Stack.Navigator>
            ) : (
              <Stack.Navigator initialRouteName="Login">
                <Stack.Screen name="Login" component={Login} />
                <Stack.Screen name="Signup" component={Signup} />
              </Stack.Navigator>
            )}
          </NavigationContainer>
        </SafeAreaView>
      </SafeAreaProvider>
  );
}

export default App;
```

Check out the final code [here](https://github.com/Balogh08/CodePlayground/tree/email_auth_setup).

Thank you for reading.

## Additional Features and Improvements

- Visibility icon for password.
- Password change option.
- Sign up with Facebook, Google, and Apple.
- Adding custom components.

## Sources

- https://rnfirebase.io/auth/usage
- https://reactnative.dev/docs/environment-setup?guide=native&platform=ios
- https://www.atomlab.dev/tutorials/integrating-firebase-react-native
- https://www.atomlab.dev/tutorials/email-authentication-react-native-firebase
- https://medium.com/@adityasinghrathore360/implementing-firebase-authentication-in-react-native-app-with-expo-a-detailed-explanation-cea4d1113501