Adding Login and Authentication Sections to your React or React Native app
React apps of a certain size or domain are likely to have sections that are restricted to logged in users. Maybe you’re building an e-commerce site that only lets registered users make purchases, or you want to make a secret section of your page where logged in users can chat amongst themselves.
Adding authentication to your app is not terribly difficult, though there are a number of ways to implement the logic. Many routing libraries have support for route navigation hooks that check if a user is logged in, but an alternate method — detailed below — uses React components to structure your app into “logged in” and “logged out” sections.
What we want to accomplish
Imagine you are building an e-commerce app powered by React or React Native. A user can browse all of the products for sale, and can add items to their cart, but can only checkout if they are logged in.
When the user lands on a route that requires them to be logged in, you first want to check if they are logged in. If they are, they can proceed to the route like normal.
If they are not logged in, the user must first go to a login/register page. At the conclusion of the login/registration flow, you should redirect them to the route they initially requested.
First approach: routing hooks
Depending on the library you are using for routing, such as react-router or react-native-router-flux, you may have out of the box support for route navigation callbacks. React Router, for instance, allows you to define an onEnter property on a route, which is a callback that fires before a user enters the route they have requested.
The onEnter property allows you to inspect the requested route and send the user to a different route based on parameters you define. See the route definition on line 18 below:
The onEnter property on the `/checkout` route means that when a user navigates to the checkout page, the corresponding requireAuth function will fire and check if a user is logged in. If the user is not, they will be redirected to the `/login` page.
There are some limitations to this workflow, however. One is the repetition of adding the onEnter property to every route that requires authentication. The other limitation is more severe. Using the onEnter pattern predicates that your requireAuth function will be able to answer the question “Is this user logged in?” If you are working in a Redux app and keep the answer to this question in your app’s state tree, it may not be easy (or it may be impossible) to get this answer from a function that is not connected to your app’s state and does not have access to Redux’s dispatch function.
An alternate approach
Instead of creating an onEnter callback and attaching it to every route that needs login protection, we can instead create a React Component that contains login logic. This component wraps all of the routes that require authenticated users, as seen on line 5 below:
EnsureLoggedInContainer is a Route component without a path prop but it behaves like any other React component. Because EnsureLoggedInContainer wraps our checkout and account routes, any logic we store inside EnsureLoggedInContainer will run before its children.
If you are working within Redux, it is easy to make your container a connected component so that you have access to your global state tree to determine if a user is logged in or not.
The necessary logic
Two components in your app need to be aware of the user’s logged in state. One is a Route component that wraps your routes that require users to be logged in. The other is your top level Route component, the parent component of all routes in your system.
The job of the EnsureLoggedInContainer is to listen for navigation to a nested route and ensure that the user is logged in. If the user is logged in, the component does nothing and simply renders its children (the requested route). If the user is not logged in, EnsureLoggedInConatainer should record the current URL for the purposes of later redirection, and then direct users to the login page.
There are two pieces of data that this container needs to grab from the global state tree: the current login state, and the current URL/path. This example uses React Router, but other platforms (i.e. React Native) or other routing libraries should have similar methods to find the current URL/path.
One important caveat: for the purposes of simplicity we assume that a user’s login state is accessible on the global state tree at `state.loggedIn`. Determining whether or not a user is a logged is something unique to every app and can depend on a bunch of different factors, such as the presence of a particular cookie or a value sent from your server. We won’t touch that subject for this tutorial.
The second and last component that needs to know about a user’s logged in is your top-level App component. It is important that this component is the parent of all routes in your system, whether or not they require users to be logged in.
Like EnsureLoggedInContainer, this container needs two pieces of data from the global state tree. One is the current login state and the other is the URL to redirect to when a user logs in.
Because the current login state is a property on the App component, you will be alerted to changes on the prop’s value with React’s componentWillUpdate and/or componentDidUpdate methods.
Below, we define a componentDidUpdate method that is called when the property changes. If the user is logging in, we redirect to the previously-set redirect URL. It is also easy to determine when a user is logging out, should you want to redirect users to the root of your app once that happens.
I believe the Route component approach is far more powerful and easier to understand than a component-property driven design. One benefit to this approach is the lack of duplication across routes and the fact that only two components need to know about a user’s current logged in state.