Authentication flows - Implementing authentication flows in React-Native using React Navigation V5

In this blog post, I'll guide you on how to implement Authentication flow using React Navigation v5.

Introduction

What is React Navigation?

React Navigation is a navigation tool which is made up of some core utilities, and those are then used by navigators to create the navigation structure in your app.

React Navigation is born from the React Native community's need for an extensible yet easy-to-use navigation solution written entirely in JavaScript (so you can read and understand all of the sources), on top of powerful native primitives.

React Navigation v5

The React Navigation v5 comes with many improvements compared to previous version v4. v5 not only provides a cross-platform native Stack behaviour, but also the API was redesigned from the ground up to allow things that were never possible before.

Authentication flow in React Navigation v5

Most apps require that a user authenticates in some way to have access to data associated with a user or other private content. Typically the flow which most of the apps across industry follows are mentioned below:

  1. The user opens the app.
  2. The app loads authentication state from persistent/local storage (for example, AsyncStorage).
  3. Upon authentication, the state is loaded, the user will be navigated to the authentication screen or the main screen, which will be entirely based on the authentication state.
  4. If the authentication was valid, then navigate the user to App main screen else to Authentication Screen.
  5. If the user signs out, we'll clear the authentication state, and we'll navigate them back to an authentication screen.

Note: I'm saying "authentication screens" because we usually have more than one. For example LoginScreen, SignUp/Register screen, ForgotPassword screen

What is the behaviour we need?

The behaviour which we want from the authentication flow is that when users sign in, we want to remove the state of the authentication flow and unmount all the screens which are related to authentication. If we press the hardware back button, we expect to not be able to go back to the authentication flow.

How it will work?

Different screens can be defined based on some condition. For instance, if the user is signed in, we define the screen: Home, Profile, Settings etc. But if the user is not signed in, we can define SignIn and SignUp screens.

For example:

isSignedIn ? (
  <>
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="Profile" component={ProfileScreen} />
    <Stack.Screen name="EditProfile" component={EditProfileScreen} />
  </>
) : (
  <>
    <Stack.Screen name="Login" component={LoginScreen} />
    <Stack.Screen name="Register" component={RegisterScreen} />
  </>
);

When we define screens like this, when isSignedIn is true, React Navigation will only see the Home, Profile and EditProfile screens, and when it's false, React Navigation will see the Login and Register screens. Which makes it impossible to navigate to the Home, Profile and EditProfile screens when the user is not signed in, and to Login and Register screens when the user is signed in.

This is the same pattern has been in use by other routing libraries such as React Router, Reach Router for a long time and is commonly known as Protected routes. The screens which need the user to be signed in are protected, and the user won't be able to navigate to protected by any means if the user is not signed in.

When the value of the variable isSignedIn changes, the magic happens. Initially, isSignedIn is false. Which means, either SignIn or SignUp screens are shown. Once the user signs in, the value of variables isSignedIn will change to true. Since the value of isSignedIn changes React Navigation will see that the SignIn and SignUp screens are no longer defined in the navigator and so it will remove them whenever the state changes the component re-renders. Then it'll automatically show the Home screen because of Home screen the first screen, which was defined when isSignedIn is true.

One important thing to note is that when using this type of setup, we no longer need to navigate to the Home screen manually by calling navigation.navigate('Home') method. React Navigation will automatically handle this and navigate to the Home screen when isSignedIn becomes true.

This is the advantage of a new feature in React Navigation v5: We are able to dynamically define the screen and alter the screen definitions of a navigator based on props or state. The example above shows stack navigator, but we can follow the same approach with any type of navigator.

Based on a variable we are able to conditionally define different screens, we are able to implement auth flow in a simple way which doesn't require additional logic to make sure that the correct screen is shown.


Handling loading state of isSignedIn

In order to implement the loading state, let's introduce a new screen SplashScreen.

SplashScreen - This screen will show a splash or loading screen when we're restoring the token from persistence/local storage. LoginScreen - This is the screen which will be shown if the user isn't signed (we couldn't find a token). HomeScreen - If the user is already signed in, then this screen will be displayed. So our navigator component will look like:

if (state.isLoading) {
  // We haven't finished checking for the token yet
  return <SplashScreen />;
}

return (
  <Stack.Navigator>
    {isSignedIn == null ? (
      // User is signed in
      <Stack.Screen name="Home" component={HomeScreen} />
    ) : (
      // No token found, user isn't signed in
      <Stack.Screen
        name="Login"
        component={LoginScreen}
        options={{
          title: "Login in",
          // When logging out, a pop animation feels natural
          // If you want the default 'push' animation you can remove this
          animationTypeForReplace: state.isSignout ? "pop" : "push",
        }}
      />
    )}
  </Stack.Navigator>
);

In the above navigator component, isLoading means that we're still checking if the user is SignedIn. This is usually done by checking if we have a token in AsyncStorage and validating if token. (AsyncStorage in async in nature) After we get the user was loggedIn, we need to set the isSignedIn.

Here, the main thing to focus on is that we're conditionally defining screens based on these state variables:

  • LoginScreen is only defined if a user is not signed in.
  • HomeScreen is only defined user is signed in.

Here, for each case, we're conditionally defining one screen. But we can also define multiple screens. For instance, we probably want to define password reset, signup, etc. screens as well when the user isn't signed in. We can use React.Fragment to define multiple screens. Similarly, for the screens which will be accessible after sign in, we'll probably have more than one screen.

Snippet of Basic Auth Flow using React Navigation v5.

Here are the code snippets of the auth flow which I'm using. Will add the link to repo soon.

App.js looks like this

import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import Navigation from "./Navigation";
import { Provider } from "react-redux";
import reduxStore from "./Utils/reduxConfig";

const store = reduxStore();

const App = () => {
  return (
    <Provider store={store}>
      <NavigationContainer>
        <Navigation />
      </NavigationContainer>
    </Provider>
  );
};

export default App;

Navigation.js looks like this

Now you have to create one action creator to save the userdata in redux when login.

const Stack = createStackNavigator();

const Navigation = (props) => {
  const [logged, setLogged] = useState(false);

  useEffect(() => {
    isLogged();
  }, [props.userdata]);

  const isLogged = () => {
    if (props.userdata && props.userdata.user) {
      setLogged(true);
    } else {
      setLogged(false);
    }
  };

  return (
    <>
      <Stack.Navigator>
        {!logged ? (
          <>
            <Stack.Screen
              name="Splash"
              component={SplashScreen}
              options={{ headerShown: false }}
            />
            <Stack.Screen name="Login" component={LoginScreen} />
            <Stack.Screen name="Signup" component={SignupScreen} />
            <Stack.Screen
              name="ForgotPassword"
              component={ForgotPasswordScreen}
            />
          </>
        ) : (
          <>
            <Stack.Screen name="Home" component={Home} />
            <Stack.Screen name="MapScreen" component={DashboardScreen} />
          </>
        )}
      </Stack.Navigator>
    </>
  );
};

Splash.js look like this

const Splash = (props) => {
  useEffect(() => {
    checkToken();
  }, []);

  const checkToken = async () => {
    const token = await AsyncStorage.getItem("token");
    const user = await AsyncStorage.getItem("user");
    const userData = JSON.parse(user);
    if (token != null) {
      props.save_user(userData);
    } else {
      props.navigation.navigate("Login");
    }
  };

  return (
    <View style={styles.container}>
      <Image
        source={require("../../assets/splash.jpeg")}
        style={styles.image}
      />
    </View>
  );
};

Conclusion

That's it for this tutorial, thanks for working all the way through this tutorial. I hope you are able to integrate the latest versions of the react-navigation.

💌 If you’d like to receive more React Native tutorials in your inbox, you can sign up for the newsletter here.

Discussions

Up next