DEV Community

Cover image for Email Authentication with Firebase in React Native for iOS and Android
Balogh Botond
Balogh Botond

Posted on

Email Authentication with Firebase in React Native for iOS and Android

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
  2. Development Stack
  3. Setting Up the React Native Environment
  4. Setting Up Firebase for Email Authentication
  5. Structuring the React Native Project
  6. Signup
  7. Logout
  8. Login
  9. Conditional Navigation Based on Authentication State
  10. Additional Features and Improvements
  11. 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

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 and start a new project. Enter a project name and accept the default settings. New project Add name Default settings Account selection
  2. Project Creation: While Firebase prepares your project, take a brief coffee break! Your new Firebase environment will be ready shortly. Project creation
  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 Email auth get started Select email auth Enable email auth Email auth enabled
  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 Add name to webapp
  5. Install Firebase in Your Project: Run npm install firebase in your project directory to add Firebase SDK.Api keys
  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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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', }], ], };
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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;
Enter fullscreen mode Exit fullscreen mode
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,
  },
});
Enter fullscreen mode Exit fullscreen mode
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>
    );
}
Enter fullscreen mode Exit fullscreen mode

Login

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

Signup

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Home

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';
Enter fullscreen mode Exit fullscreen mode

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);
        });
    };
Enter fullscreen mode Exit fullscreen mode

For the full document please check Signup.js

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

Signed up user

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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);
            });
    };
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

Check out the final code here.

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

Top comments (0)