How to manage Authentication flow with react-navigation v5 and Firebase

2020-03-12

Managing User Authentication in your mobile apps can be a fundamental requirement to allow the user to access data only if they are authorized. The react-navigation library in its latest version (version 5) allows you to implement a custom authentication flow in React Native apps.

In this tutorial, let us discuss one of the strategies to implement an authentication flow using react-navigation library, and react-native-firebase. Following along, you are going to set up and configure a Firebase project to implement anonymous sign-in functionality. Lastly, you will be able to take advantage of the latest version of the react-native-firebase package.

Requirements

The following requirements are going to make sure you have a suitable development environment:

  • Node.js above 10.x.x installed on your local machine
  • JavaScript/ES6 basics
  • watchman the file watcher installed
  • react-native-cli installed through npm or access via npx

For a complete walkthrough on how you can set up a development environment for React Native, you can go through official documentation here.

Also, do note that the following tutorial is going to use the react-native version 0.61.5. Please make sure you are using a version of React Native above 0.60.x.

Installing and configuring up react-navigation

To start, create a new React Native project and install the dependencies to set up and use the react-navigation library.

1# create a new project
2npx react-native init authFlow
3
4cd authFlow
5
6# install core navigation dependencies
7yarn add @react-navigation/native @react-navigation/stack react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

From React Native 0.60.x and higher, linking is automatic so you don't need to run react-native link.

To finalize the installation, on iOS, you have to install pods. (Note: Make sure you have Cocoapods installed.)

1cd ios/ && pod install
2
3# after pods are installed
4cd ..

Similarly, on Android, open file android/app/build.gradle and add the following two lines in dependencies section:

1implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
2implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'

Lastly, to save the app from crashing in a production environment, add the following line in index.js.

1import 'react-native-gesture-handler'
2import { AppRegistry } from 'react-native'
3import App from './App'
4import { name as appName } from './app.json'
5
6AppRegistry.registerComponent(appName, () => App)

That's it for configuring up the react-navigation library in your React Native app.

Create a new Firebase Project

To access the Firebase credentials for each mobile OS platform and configure them to use Firebase SDK, create a new Firebase project or use one if you have access already from Firebase console, you can skip this step.

Create a new project as shown below.

hb1

Complete the details of your Firebase project:

hb2

Click the button Create project and you are going to be redirected to the dashboard screen. That's it to create a new Firebase project.

Add Firebase SDK to React Native app

If you have used react-native-firebase version 5 or below, you must have noticed that it was a monorepo that used to manage all Firebase dependencies from one module.

Version 6 of this library wants you to only install those dependencies based on Firebase features that you want to use. For example, in the current app, to support the anonymous login feature you are going to start by the auth and core app package only.

Also, do note that the core module: @react-native-firebase/app is always required.

Open a terminal window to install these dependencies.

1yarn add @react-native-firebase/app @react-native-firebase/auth

Go back to the Firebase console of the project and navigate to Authentication section from the side menu.

cb7

Go to the second tab Sign-in method and make sure to enable the Anonymous sign-in provider.

cb8

cb9

Add Firebase credentials to your iOS app

Firebase provides a file called GoogleService-Info.plist that contains all the API keys as well as other credentials for iOS devices to authenticate the correct Firebase project.

To get these credentials, go to back to the Firebase console, from the dashboard screen of your Firebase project, open Project settings from the side menu.

hb3

Go to Your apps section and click on the icon iOS to select the platform.

cb1

Enter the application details and click on Register app.

cb2

Then download the GoogleService-Info.plist file as shown below.

cb3

Open Xcode, then open the file /ios/authFlow.xcodeproj file. Right-click on project name and Add Files option, then select the file to add to this project.

hb4

hb5

Then, open ios/authFlow/AppDelegate.m and add the following header.

1#import <Firebase.h>

Within the didFinishLaunchingWithOptions method, add the following configure method:

1- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
2 if ([FIRApp defaultApp] == nil) {
3 [FIRApp configure];
4 }

Lastly, go back to the terminal window to install pods.

1cd ios/ && pod install
2
3# after pods are installed
4cd ..

Make sure you build the iOS app.

1npx react-native run-ios

Add Firebase credentials to your Android app

For Android devices, Firebase provides a google-services.json file that contains all the API keys as well as other credentials for Android devices to authenticate the correct Firebase project.

Go to Your apps section and click on the icon Android to select the platform.

cb1

Then download the google-services.json file as shown below.

cb6

Now copy the downloaded JSON file to your React Native project at the following location: /android/app/google-services.json.

hb6

Then open android/build.gradle file and add the following:

1dependencies {
2 // ...
3 classpath 'com.google.gms:google-services:4.2.0'
4 }

Then, open android/app/build.gradle file and at the very bottom of this file, add the following:

1apply plugin: 'com.google.gms.google-services'

Make sure you build the Android app.

1npx react-native run-android

Set up a Login Screen

Create a new file called Login.js inside the directory src/screens/. This screen component is going to be responsible to display a login button and authenticate the user if they have signed out of the app.

Add the following code snippet to this file:

1import React from 'react'
2import { View, StyleSheet, Text, TouchableOpacity } from 'react-native'
3
4export default function Login() {
5 // TODO: add firebase login function later
6
7 return (
8 <View style={styles.container}>
9 <Text style={styles.title}>Welcome</Text>
10 <TouchableOpacity
11 style={styles.button}
12 onPress={() => alert('Anonymous login')}
13 >
14 <Text style={styles.buttonText}>Login Anonymously 🔥</Text>
15 </TouchableOpacity>
16 </View>
17 )
18}
19
20const styles = StyleSheet.create({
21 container: {
22 flex: 1,
23 justifyContent: 'center',
24 alignItems: 'center',
25 backgroundColor: '#ffe2ff'
26 },
27 title: {
28 marginTop: 20,
29 marginBottom: 30,
30 fontSize: 28,
31 fontWeight: '500',
32 color: '#7f78d2'
33 },
34 button: {
35 flexDirection: 'row',
36 borderRadius: 30,
37 marginTop: 10,
38 marginBottom: 10,
39 width: 300,
40 height: 60,
41 justifyContent: 'center',
42 alignItems: 'center',
43 backgroundColor: '#481380'
44 },
45 buttonText: {
46 color: '#ffe2ff',
47 fontSize: 24,
48 marginRight: 5
49 }
50})

Add two stack navigators

Inside the directory src/navigation/ and create two new files:

  • SignInStack.js
  • SignOutStack.js

Both of these files are self-explanatory by their names. Their functionality is going to contain screens related to the authenticated state of the app. For example, the SignOutStack.js is going to have a stack navigator have a screen file (such as Login.js) to show user info when they are not authorized to enter the app.

To get more details about what a stack navigator is and how to use it, check out the previous post here.

Open SignOutStack.js file and using NavigatorContainer and an instance of createStackNavigator, implement the following code snippet:

1import * as React from 'react'
2import { NavigationContainer } from '@react-navigation/native'
3import { createStackNavigator } from '@react-navigation/stack'
4import Login from '../screens/Login'
5
6const Stack = createStackNavigator()
7
8export default function SignOutStack() {
9 return (
10 <NavigationContainer>
11 <Stack.Navigator headerMode="none">
12 <Stack.Screen name="Login" component={Login} />
13 </Stack.Navigator>
14 </NavigationContainer>
15 )
16}

This is how navigators are defined declaratively using version 5 of react-navigation. It follows a more component based approach, similar to that of react-router in web development (only if you are familiar with it).

Add the following code snippet to SignInStack.js file:

1import * as React from 'react'
2import { NavigationContainer } from '@react-navigation/native'
3import { createStackNavigator } from '@react-navigation/stack'
4import Home from '../screens/Home.js'
5
6const Stack = createStackNavigator()
7
8export default function SignInStack() {
9 return (
10 <NavigationContainer>
11 <Stack.Navigator headerMode="none">
12 <Stack.Screen name="Home" component={Home} />
13 </Stack.Navigator>
14 </NavigationContainer>
15 )
16}

If the user is authorized they are going to have access to the Home screen. Create another file in src/screens/ called Home.js and add the following code snippet:

1import React from 'react'
2import { View, StyleSheet, Text, TouchableOpacity } from 'react-native'
3
4export default function Home() {
5 // TODO: add firebase sign-out and user info function later
6
7 return (
8 <View style={styles.container}>
9 <Text style={styles.title}>Welcome user!</Text>
10 <TouchableOpacity style={styles.button} onPress={() => alert('Sign out')}>
11 <Text style={styles.buttonText}>Sign out 🤷</Text>
12 </TouchableOpacity>
13 </View>
14 )
15}
16
17const styles = StyleSheet.create({
18 container: {
19 flex: 1,
20 justifyContent: 'center',
21 alignItems: 'center',
22 backgroundColor: '#ffe2ff'
23 },
24 title: {
25 marginTop: 20,
26 marginBottom: 30,
27 fontSize: 28,
28 fontWeight: '500',
29 color: '#7f78d2'
30 },
31 button: {
32 flexDirection: 'row',
33 borderRadius: 30,
34 marginTop: 10,
35 marginBottom: 10,
36 width: 160,
37 height: 60,
38 justifyContent: 'center',
39 alignItems: 'center',
40 backgroundColor: '#481380'
41 },
42 buttonText: {
43 color: '#ffe2ff',
44 fontSize: 24,
45 marginRight: 5
46 }
47})

Set up an authentication flow

To complete the navigation in the current React Native app, let us set up an authentication flow in this section. Create a new file src/navigation/AuthNavigator.js and to import the following as well as both stack navigators created in the previous section.

1import React, { useState, useEffect, createContext } from 'react'
2import auth from '@react-native-firebase/auth'
3import SignInStack from './SignInStack'
4import SignOutStack from './SignOutStack'

Create an AuthContext that is going to expose the user data to only those screens when the user successfully logs in, that is, the screens that are part of the SignInStack navigator.

1export const AuthContext = createContext(null)

Then, define the state variables initializing and user inside the functional component AuthNavigator. The initializing state variable is going to be true by default and keeps track of the changes in the authentication state. It will be false when the user's authentication state changes. The change in the state is going to be handle by the helper method, onAuthStateChanged.

It is important to subscribe to the auth changes when this functional component is mounted. This is done by using the useEffect hook. When this component unmounts, unsubscribe it.

Lastly, make sure to pass the value of user data using the AuthContext.Provider. Here is the complete snippet:

1export default function AuthNavigator() {
2 const [initializing, setInitializing] = useState(true)
3 const [user, setUser] = useState(null)
4
5 // Handle user state changes
6 function onAuthStateChanged(result) {
7 setUser(result)
8 if (initializing) setInitializing(false)
9 }
10
11 useEffect(() => {
12 const authSubscriber = auth().onAuthStateChanged(onAuthStateChanged)
13
14 // unsubscribe on unmount
15 return authSubscriber
16 }, [])
17
18 if (initializing) {
19 return null
20 }
21
22 return user ? (
23 <AuthContext.Provider value={user}>
24 <SignInStack />
25 </AuthContext.Provider>
26 ) : (
27 <SignOutStack />
28 )
29}

Now, to make it work, open App.js file and modify it as below:

1import React from 'react'
2import AuthNavigator from './src/navigation/AuthNavigator'
3
4const App = () => {
5 return <AuthNavigator />
6}
7
8export default App

You are going to get a similar output as shown below:

hb7

Add the login functionality

Open screens/Login.js file and start by importing the auth module.

1// ... rest of the import statements
2import auth from '@react-native-firebase/auth'

Inside the Login functional component, add a helper method signIn() that is going to be triggered when the user presses the sign-in button on this screen. This method is going to be an asynchronous function.

1async function signIn() {
2 try {
3 await auth().signInAnonymously()
4 } catch (e) {
5 switch (e.code) {
6 case 'auth/operation-not-allowed':
7 console.log('Enable anonymous in your firebase console.')
8 break
9 default:
10 console.error(e)
11 break
12 }
13 }
14}

Add this helper method as the value of the proponPress for TouchableOpacity.

1<TouchableOpacity style={styles.button} onPress={signIn}>
2 {/* rest remains same */}
3</TouchableOpacity>

Add log out functionality

To add a log out functionality, open screens/Home.js file and import Firebase auth module as well as AuthContext from navigator/AuthNavigator.js file. The AuthContext is going to help us verify that the user is being signed in. Also, import the useContext hook from React.

1import React, { useContext } from 'react'
2import { View, StyleSheet, Text, TouchableOpacity } from 'react-native'
3import auth from '@react-native-firebase/auth'
4import { AuthContext } from '../navigation/AuthNavigator'

Next, in the functional component Home using the hook, store the user object coming from Firebase. Also, define an asynchronous helper method logOut().

Make sure in the JSX returned from this component, the uid or the unique user id from the Firebase is displayed as well as the value of the prop onPress is the helper method.

1export default function Home() {
2 const user = useContext(AuthContext)
3
4 async function logOut() {
5 try {
6 await auth().signOut()
7 } catch (e) {
8 console.error(e)
9 }
10 }
11
12 return (
13 <View style={styles.container}>
14 <Text style={styles.title}>Welcome {user.uid}!</Text>
15 <TouchableOpacity style={styles.button} onPress={logOut}>
16 <Text style={styles.buttonText}>Sign out 🤷</Text>
17 </TouchableOpacity>
18 </View>
19 )
20}

Here is the complete output of the app:

hb8

At this point, if you head back to the Firebase console, you can verify the same user id of the last logged in user.

hb9

Conclusion

Thanks for going through this tutorial. I hope you had fun integrating the latest version of react-navigation and react-native-firebase libraries.

For more info to manage Authentication flows, you can refer to the official documentation of react-navigation here.


Originally published at Heartbeat.

I'm Aman working as an independent fullstack developer with technologies such as Node.js, ReactJS, and React Native. I try to document and write tutorials to help JavaScript, Web and Mobile developers.