React Navigation Switch Navigator and Authentication Flow

Authentication flow is basically a password-protected screen or a set of screens to display the user associated data or private content. Probably, you have seen many apps that require the user to login to access the app. I think it’s fair enough to understand the authentication flow. Let’s jump to the development section to better understand.

We are building an app that requires a login to access the user screen. The user screen created with bottom tab navigator and holds the home, settings, and logout tabs. The logout tab will clear the stored authentication token and redirect the user to the sign-in screen.

Table of content

Install Required Packages

We will use the Switch Navigator to implement the Authentication flow. We are using the React Navigation Stack, React Navigation Tabs along with React Navigation 4.

"react-navigation": "^4.0.2",
"react-navigation-stack": "^1.5.4",
"react-navigation-tabs": "^2.4.1",

Create Auth Flow Screens

If you are working with the expo, then the expo does not let you call to your localhost API server. I have used the ngrok to make it working through my localhost server.

Let’s start by creating the SigninScreen. This screen will hold the API calls logic to authenticate the user. We are using the AsyncStorage to store the authentication token and the Authenticated User Name.

SigninScreen

import React from 'react';
import Dimensions from 'Dimensions';
import { 
    KeyboardAvoidingView, View, Button, Alert, Text, AsyncStorage, StyleSheet, TextInput
} from 'react-native';


export default class SigninScreen extends React.Component {

    static navigationOptions = {
        header: null,
    };

    constructor(props) {
        super(props);

        this.state = {
            email: '', 
            password: '', 
            spinner: false
        };

        this._signInHandler = this._signInHandler.bind(this);
    }

    _signInHandler = async () => {
        const {email, password} = this.state;

        var formData = new FormData();
        formData.append('email', email);
        formData.append('password', password);

        this.setState({spinner: true});

        const response = await fetch(`https://c282a758.ngrok.io/api/login`, {
            method: 'POST', 
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded'
            }, 
            body: formData
        })
        .then(resp => {
            this.setState({spinner: false});
            return resp.json();
        })
        .catch(error => {
            this.setState({spinner: false});
            throw error;
        });

        if (typeof response.message != "undefined") {
            await Alert.alert('Error', response.message);
        }
        else {
            await AsyncStorage.setItem('userToken', response.token);
            await AsyncStorage.setItem('userName', response.userData.name);
            this.props.navigation.navigate('App');
        }
    }

    render() {
        return (
            <KeyboardAvoidingView style={{flexGrow: 1}} behavior="padding" enabled>
                <View style={style.container}>
                    <TextInput 
                        keyboardType="email-address"
                        onChangeText={email => this.setState({email})}
                        style={style.input}
                        placeholder="Email Address"
                        value={this.state.email}
                    />
                    <TextInput 
                        secureTextEntry={true}
                        onChangeText={password => this.setState({password})}
                        style={style.input}
                        placeholder="Password"
                        value={this.state.password}
                    />
                    {this.state.spinner &&
                        <Text style={style.spinnerTextStyle}>Processing ...</Text>
                    }
                    {!this.state.spinner &&
                        <Button
                            title="Sign in!"
                            onPress={this._signInHandler}
                        />
                    }
                </View>
            </KeyboardAvoidingView>
        );
    }
}

const DEVICE_WIDTH = Dimensions.get('window').width;

const style = StyleSheet.create({
    container: {
        flex: 1, 
        justifyContent: 'center', 
        alignItems: 'center'
    }, 
    input: {
        backgroundColor: '#DAE1F1',
        width: DEVICE_WIDTH - 100,
        height: 40,
        marginHorizontal: 20,
        borderRadius: 20,
        color: '#333333',
        marginBottom: 30,
        paddingLeft: 15
    },
    spinnerTextStyle: {
        textAlign: 'center'
    },
});

The _signInHandler async method will grab the authentication token and store it through AsyncStorage and redirect us to App stack navigator. We have used the Alert to display the basic un-authentication errors. We will implement the Stack Navigator in the App.js file.

TabScreen.js

import React from 'react'
import { View, Text, AsyncStorage, StyleSheet } from 'react-native'
import { Ionicons, SimpleLineIcons } from '@expo/vector-icons'
import { createBottomTabNavigator } from 'react-navigation-tabs'

import HomeScreen from './HomeScreen';
import SettingScreen from './SettingScreen';

const SignoutScreen = () => {}

const style = StyleSheet.create({
    container: {
        flex: 1, 
        justifyContent: 'center', 
        alignItems: 'center'
    }
});

export const TabScreen = createBottomTabNavigator({
    Home: {
        screen: HomeScreen, 
        navigationOptions: {
            tabBarLabel: 'Home', 
            tabBarIcon: ({ tintColor }) => (
                <Ionicons name="ios-home" color={tintColor} size={25} />
            )
        }
    }, 
    Settings: {
        screen: SettingScreen, 
        navigationOptions: {
            tabBarLabel: 'Settings', 
            tabBarIcon: ({ tintColor }) => (
                <Ionicons name="ios-settings" color={tintColor} size={25} />
            )
        }
    }, 
    Signout: {
        screen: SignoutScreen, 
        navigationOptions: {
            tabBarLabel: 'Signout', 
            tabBarIcon: ({ tintColor }) => (
                <SimpleLineIcons name="logout" color={tintColor} size={20} />
            ), 
            tabBarOnPress: async ({navigation}) => {
                await AsyncStorage.clear();
                navigation.navigate('Auth');
            }
        }
    }
}, {
    tabBarOptions: {
        activeTintColor: 'red', 
        inactiveTintColor: 'grey', 
        showIcon: true
    }
});

I have shared a separated post to better understand for bottom tab navigator. The main thing to notice here is the signout screen. I have removed the stored authentication token on the signout event. And redirect the user to the Auth stack navigator.

HomeScreen and SettingScreen

Let’s quickly set up the HomeScreen first. This screen just displays the logged-in user name and the screen name.

import React from 'react';
import {StyleSheet, View, Text, AsyncStorage} from 'react-native';

export default class HomeScreen extends React.Component {

    constructor() {
        super();
        this.state = {
            name: ''
        };
        this._bootstrap();
    }

    _bootstrap = async () => {
        const userName = await AsyncStorage.getItem('userName');
        this.setState({name: userName});
    }

    render() {
        return (
            <View style={styles.container}>
                <Text>Welcome {this.state.name}</Text>
                <Text>to Home Screen</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1, 
        justifyContent: 'center', 
        alignItems: 'center'
    }
});

SettingScreen.js

import React from 'react';
import {StyleSheet, View, Text, AsyncStorage} from 'react-native';

export default class SettingScreen extends React.Component {

    constructor() {
        super();
        this.state = {
            name: ''
        };
        this._bootstrap();
    }

    _bootstrap = async () => {
        const userName = await AsyncStorage.getItem('userName');
        this.setState({name: userName});
    }

    render() {
        return (
            <View style={styles.container}>
                <Text>Welcome {this.state.name}</Text>
                <Text>to Setting Screen</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1, 
        justifyContent: 'center', 
        alignItems: 'center'
    }
});

The next step is creating a screen that will check the stored authentication token and will redirect the user to the particular stack navigator. We are using the ActivityIndicator to display a loader while the code checks the authentication token.

AuthLoadingScreen

import React from 'react'
import { View, StatusBar, ActivityIndicator, AsyncStorage, StyleSheet } from 'react-native'

export default class AuthLoadingScreen extends React.Component {

    constructor() {
        super();
        this._bootstrap();
    }

    _bootstrap = async () => {

        const userToken = await AsyncStorage.getItem('userToken');
        this.props.navigation.navigate(userToken ? 'App' : 'Auth');
    }

    render() {
        return (
            <View style={styles.container}>
                <ActivityIndicator />
                <StatusBar barStyle="default" />
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
    },
});

The _bootstrap() method here to notice. This is where we check the auth token and redirect the user to the particular stack.

Implement the Authentication Flow

We have prepared all the screen. Now the next step is to implement the authentication flow. First, open the App.js file and import all the screens and the required packages.

import { TabScreen } from './src/screens/TabScreen'
import SignScreen from './src/screens/auth/SigninScreen'
import AuthLoadingScreen from './src/screens/AuthLoadingScreen'

import { createAppContainer, createSwitchNavigator } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'

Next is to create two stack navigator that we have used on previously created screens. The first one is for the sign-in screen and the second is for the tab screen.

const AppStack = createStackNavigator({ TabScreen });
const AuthStack = createStackNavigator({ Signin: SignScreen });

The next and final step is to create the app container with the help of a switch navigator.

export default createAppContainer(createSwitchNavigator(
    {
        Starter: AuthLoadingScreen, 
        App: AppStack, 
        Auth: AuthStack
    }, 
    {
        initialRouteName: 'Starter'
    }
));

The only thing is to notice here is the initialRouteName. The AuthLoading stack will be the first stack when we open our app. So that the app every time check the authentication token and redirect the user to the desired screen.