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
- Prerequisites
- Development Stack
- Setting Up the React Native Environment
- Setting Up Firebase for Email Authentication
- Structuring the React Native Project
- Signup
- Logout
- Login
- Conditional Navigation Based on Authentication State
- Additional Features and Improvements
- 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
- Start a New Project: Navigate to the Firebase Console and start a new project. Enter a project name and accept the default settings.
- Project Creation: While Firebase prepares your project, take a brief coffee break! Your new Firebase environment will be ready shortly.
- 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.
- 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.
-
Install Firebase in Your Project: Run
npm install firebase
in your project directory to add Firebase SDK. -
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>
);
}
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>
);
}
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>
);
}
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
Try out, and sign up as a user. Check the Firebase authentication users tab. There is the 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>
);
}
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
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.
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
More about the author at LUNitECH
Top comments (0)