Interested in reading this tutorial as one of many chapters in my advanced React with Firebase book? Checkout the entire The Road to Firebase book that teaches you to create business web applications without the need to create a backend application with a database yourself.
This comprehensive tutorial walks you through a real-world application using React and Firebase. React is used to display applications in web browsers and to store local state in components, while Firebase is used for authentication, authorization, and managing a realtime database.
After youâve mastered the basics of React, I always recommend moving on to advanced topics like authentication, authorization, and connecting React applications to databases. These operations make up the fundamentals real business applications need. Donât worry about implementing the backend application that manages it all yourself, as Firebase provides the perfect alternative. I have seen real businesses go from zero to profitable with only React and Firebase as their tools, myself included. No backend application with Node.js was needed, and this tutorial was created to show you how.
50% of this tutorialâs outcome can seen here . Security reasons prevent me from showing everything there, though the remaining material can be found in the book. To keep the guide updated, here is a list of the primary libraries and their versions used in this tutorial:
Please let me know if the tutorial needs any updates for others learning about the topic, and donât hesitate to point out improvements in the comments, or you can visit the article directly on GitHub to open issues or pull requests.
The requirements for this tutorial are a working editor or IDE/terminal , and recent versions of node and npm . You should have learned about React in the first place. The Road to learn React is a free ebook that provides all the fundamentals of React. You will build a larger application in plain React, and transition from JavaScript ES5 to JavaScript ES6 and beyond. This tutorial will not dive into all the details taught in the ebook, so take the chance to grab your copy of it to learn those first.
Letâs get started with the React + Firebase application we are going to build together. The application should be the perfect starter project to realize your ideas. It should be possible to display information with React, to navigate from URL to URL with React Router and to store and retrieve data with Firebase. Also the application will have everything thatâs needed to register, login and logout users. In the end, you should be able to implement any feature on top of this application to create well-rounded React applications.
If you lack information on how to setup your React development environment, checkout these setup guides for MacOS and Windows . Now, there are two ways to begin with this application: either follow my guidance in this section; or find a starter project in this GitHub repository and follow its installation instructions. This section will show how to set up the same project from scratch, whereas the starter project grants instant access without setting up the folder/file structure yourself.
The application we are going to build with React and Firebase will be set up with Facebookâs official React boilerplate project, called create-react-app . You can set up your project with it on the command line whereas the name for the project is up to you. Afterward, navigate on the command line into the project:
Now you have the following command on your command line to start your application. You can start your application and visit it in the browser:
Now weâll set up the project for our needs. First, get rid of the files from the boilerplate React project, since we wonât be using them. From the command line, head to your src/ folder and execute it:
Second, create a components/ folder in your applicationâs src/ folder on the command line. This is where all your components will be implemented. Also, the App component that you have removed in the previous step will be recreated here:
Create a dedicated folder for each component we will implement for this application. For the sake of readability, I split up the commands into multiple lines:
In each folder, create an index.js file for the component. Navigate into a folder, create the file, and navigate out again. Repeat these steps for every component. You can choose to name your folders/files differently, but thatâs how I liked to do it for my applications.
Next, implement a basic React component for each file you created. For the App component in src/components/App/index.js , it could look like the following:
Fix the relative path to the App component in the src/index.js file. Since you have moved the App component to the src/components folder, you need to add the /components subpath to it.
Then, create one more folder in your src/ folder:
The folder should be located next to src/components/ . Move into src/constants/ and create two files for the applicationâs routing and roles management later:
The application with its folders and files is set up, and you can verify this by running it on the command line and accessing it through a browser. Check the starter project on GitHub I linked in the beginning of this section to verify whether you have set up everything properly.
Since we are building a larger application in the following sections, it would be great to have a couple of pages (e.g. landing page, account page, admin page, sign up page, sign in page) to split the application into multiple URLs (e.g. /landing, /account, /admin). These URLs or subpaths of a domain are called routes in a client-side web application. Letâs implement the routing with React Router before we dive into Firebase for the realtime database and authentication/authorization. If you havenât used React Router before, it should be straightforward to pick up the basics throughout building this application.
The application should have multiple routes. For instance, a user should be able to visit a public landing page, and also use sign up and sign in pages to enter the application as an authenticated user. If a user is authenticated, it is possible to visit protected pages like account or admin pages whereas the latter is only accessible by authenticated users with an admin role. You can consolidate all the routes of your application in a well-defined src/constants/routes.js constants file:
Each route represents a page in your application. For instance, the sign up page should be reachable in development mode via http://localhost:3000/signup and in production mode via http://yourdomain/signup .
First, you will have a sign up page (register page) and a sign in page (login page). You can take any web application as the blueprint to structure these routes for a well-rounded authentication experience. Take the following scenario: A user visits your web application, is convinced by your service, and finds the button in the top-level navigation to sign in to your application. But the user has no account yet, so a sign up button is presented as an alternative on the sign in page.
Second, there will be a landing page and a home page . The landing page is your default route (e.g. http://yourdomain/ ). Thatâs the place where a user ends up when visiting your web application. The user doesnât need to be authenticated to go this route. On the other hand, the home page is a protected route , which users can only access if they have been authenticated. You will implement the protection of the route using authorization mechanisms for this application.
Third, next to the home page , there will be protected account page and admin page as well. On the account page, a user can reset or change a password. It is secured by authorization as well, so it is only reachable for authenticated users. On the admin page, a user authorized as admin will be able to manage this applicationâs users. The admin page is protected on a more fine-grained level, because it is only accessible for authenticated admin users.
Lastly, the password forget component will be exposed on another non-protected page, a password forget page , as well. It is used for users who are not authenticated and forgot about their password.
Weâve completed the routes for this React with Firebase application. I find it exciting to build a well-rounded application with you, because it can be used as a boilerplate project that gives you authentication, authorization, and a database. These are foundational pillars for any web-based application.
Now, all these routes need to be accessible to the user. First, you need a router for your web application, which is responsible to map routes to React components. React Router is a popular package to enable routing, so install it on the command line:
The best way to start is implementing a Navigation component that will be used in the App component. The App component is the perfect place to render the Navigation component, because it always renders the Navigation component but replaces the other components (pages) based on the routes. Basically, the App component is the container where all your fixed components are going (e.g. navigation bar, side bar, footer), but also your components that are displayed depending on the route in the URL (e.g. account page, login page, password forget page).
First, the App component will use the Navigation component that is not implemented yet. Also, it uses the Router component provided by React Router. The Router makes it possible to navigate from URL-to-URL on the client-side application without another request to a web server for every route change. The application is only fetched once from a web server, after which all routing is done on the client-side with React Router.
In src/components/App/index.js file:
Second, implement the Navigation component. It uses the Link component of React Router to enable navigation to different routes. These routes were defined previously in your constants file. Letâs import all of them and give every Link component a specific route.
In src/components/Navigation/index.js file:
Now, run your application again and verify that the links show up in your browser, and that once you click a link, the URL changes. Notice that even though the URL changes, the displayed content doesnât change. The navigation is only there to enable navigation through your application. But no one knows what to render on each route. Thatâs where the route to component mapping comes in. In your App component, you can specify which components should show up according to corresponding routes with the help of the Route component from React Router.
If a route matches a path prop, the respective component will be displayed; thus, all the page components in the App component are exchangeable by changing the route, but the Navigation component stays fixed independently of any route changes. This is how you enable a static frame with various components (e.g. Navigation) around your dynamic pages driven by routes. Itâs all made possible by Reactâs powerful composition .
Previously, you created basic components for each page component used by our routes. Now you should be able to start the application again. When you click through the links in the Navigation component, the displayed page component should change according to the URL. The routes for the PasswordForget and SignUp components are not used in the Navigation component, but will be defined elsewhere later. For now, you have successfully implemented fundamental routing for this application.
The main focus here is using Firebase in React for the application weâll build together. Firebase, bought by Google in 2014, enables realtime databases, extensive authentication and authorization, and even for deployment. You can build real-world applications with React and Firebase without worrying about implementing a backend application. All the things a backend application would handle, like authentication and a database, is handled by Firebase. Many businesses use React and Firebase to power their applications, as it is the ultimate combination to launch an MVP .
To start, sign up on the official Firebase website . After you have created a Firebase account, you should be able to create projects and be granted access to the project dashboard. Weâll begin by creating a project for this application on their platform whereas the project can have any name. In the case of this application, run it on the free pricing plan. If you want to scale your application later, you can change the plan. Follow this visual Firebase setup and introduction guide to learn more about Firebaseâs dashboard and features. It would also give you first guidelines on how to acivate Firebaseâs Realtime Database instead of Firebaseâs Cloud Firestore.
Next, find the projectâs configuration in the settings on your projectâs dashboard. There, youâll have access to all the necessary information: secrets, keys, ids and other details to set up your application. Copy these in the next step to your React application.
Sometimes the Firebase website doesnât make it easy to find this page. Since itâs moved around with every iteration of the website, I cannot give you any clear advice where to find it on your dashboard. This is an opportunity to familiarize yourself with Firebase projectâs dashboard while you search for the configuration.
Now that weâve completed the Firebase setup, you can return to your application in your editor/IDE to add the Firebase configuration. First, install Firebase for your application on the command line:
Next, weâll create a new file for the Firebase setup. We will use a JavaScript class to encapsulate all Firebase functionalities, realtime database, and authentication, as a well-defined API for the rest of the application. You need only instantiate the class once, after which it can use it then to interact with the Firebase API, your custom Firebase interface.
Letâs start by copying the configuration from your Firebase projectâs dashboard on their website to your application as a configuration object in a new src/components/Firebase/firebase.js file. Make sure to replace the capitalized keys with the corresponding keys from your copied configuration:
As alternative, you can also use environment variables in React applications, but you have to use the REACT_APP prefix when you use create-react-app to set up the application:
Now you can define the environmental variables in a new .env file in your projectâs root folder. The .env file can also be added to your .gitginore file (in case you are using git), so your Firebase credentials are not exposed publicly on a platform like GitHub.
Both ways work. You can define the configuration inline in source code or as environment variables. Environmental variables are more secure, and should be used when uploading your project to a version control system like git, though we will be continuing with the Firebase setup. Import firebase from the library you installed earlier, and then use it within a new Firebase class to initialize firebase with the configuration:
Thatâs all that is needed for a firebase configuration in your application. Optionally, you can create a second Firebase project on the Firebase website to have one project for your development environment and one project for your production environment. That way, you never mix data in the Firebase database in development mode with data from your deployed application (production mode). If you decide to create projects for both environments, use the two configuration objects in your Firebase setup and decide which one you take depending on the development/production environment:
An alternate way to implement this is to specify a dedicated .env.development and .env.production file for both kinds of environment variables in your project. Each file is used to define environmental variables for the matching environment. Defining a configuration becomes straightforward again, because you donât have to select the correct configuration yourself.
Whether you used environment variables, defined the configuration inline, used only one Firebase project, or multiple projects for each environment, you configured Firebase for your React application. The next section will show you how a Firebase instance created from the Firebase class is used in React.
You created a Firebase class, but you are not using it in your React application yet. In this section, weâll connect the Firebase with the React world. The simple approach is to create a Firebase instance with the Firebase class, and then import the instance (or class) in every React component where itâs needed. Thatâs not the best approach though, for two reasons:
An alternative way is to use Reactâs Context API to provide a Firebase instance once at the top-level of your component hierarchy. Create a new src/components/Firebase/context.js file in your Firebase module and provide the following implementation details:
The createContext() function essentially creates two components. The FirebaseContext.Provider component is used to provide a Firebase instance once at the top-level of your React component tree, which we will do in this section; and the FirebaseContext.Consumer component is used to retrieve the Firebase instance if it is needed in the React component. For a well-encapsulated Firebase module, weâll define a index.js file in our Firebase folder that exports all necessary functionalities (Firebase class, Firebase context for Consumer and Provider components):
The Firebase Context from the Firebase module (folder) is used to provide a Firebase instance to your entire application in the src/index.js file. You only need to create the Firebase instance with the Firebase class and pass it as value prop to the Reactâs Context:
Doing it this way, we can be assured that Firebase is only instantiated once and that it is injected via Reactâs Context API to Reactâs component tree. Now, every component that is interested in using Firebase has access to the Firebase instance with a FirebaseContext.Consumer component. Even though you will see it first-hand later for this application, the following code snippet shows how it would work:
Firebase and React are now connected, the fundamental step to make the layers communicate with each other. Next, we will implement the interface for the Firebase class on our side to communicate with the Firebase API.
In the previous section, you created a Firebase project on the official Firebase website. This section will implement the interface of your Firebase class that enables communication between the class and the Firebase authentication API. In the sections afterward, you will use the interface of the Firebase class in your React components.
First, we need to activate one of the available authentication providers on Firebaseâs website. On your projectâs Firebase dashboard, you can find a menu item which says âAuthenticationâ. Select it and click âSign-In Methodâ menu item afterward. There you can enable the authentication with Email/Password:
Second, we will implement the authentication API for our Firebase class. Import and instantiate the package from Firebase responsible for all the authentication in your src/components/Firebase/firebase.js file:
Letâs define all the authentication functions as class methods step by step. They will serve our communication channel from the Firebase class to the Firebase API. First, the sign up function (registration) takes email and password parameters for its function signature and uses an official Firebase API endpoint to create a user:
Weâll also set up the login/sign-in function, which takes email and password parameters, as well:
These endpoints are called asynchronously, and they will need to be resolved later, as well as error handling. For instance, it is not possible to sign in a user who is not signed up yet since the Firebase API would return an error. In case of the sign out function, you donât need to pass any argument to it, because Firebase knows about the currently authenticated user. If no user is authenticated, nothing will happen when this function is called.
There are two more authentication methods to reset and change a password for an authenticated user:
Thatâs the authentication interface for your React components that will connect to the Firebase API. In the next section, we will consume all the methods of your Firebase class in your React components.
We set up all the routes for your application, configured Firebase and implemented the authentication API for your Firebase class. Itâs also possible to use Firebase within your React components. Now itâs time to use the authentication functionalities in your React components, which weâll build from scratch. I try to put most of the code in one block, because the components are not too small, and splitting them up step by step might be too verbose. Nevertheless, I will guide you through each code block afterward. The code blocks for forms can become repetitive, so they will be explained once well.
Letâs start with the sign up page (registration page). It consists of the page, a form, and a link. The form is used to sign up a new user to your application with username, email, and password. The link will be used on the sign in page (login page) later if a user has no account yet. It is a redirect to the sign up page, but not used on the sign up page itself. Implement the src/components/SignUp/index.js file the following way:
The SignUpForm component is the only React class component in this file, because it has to manage the form state in Reactâs local state. There are two pieces missing in the current SignUpForm component: the form content in the render method in terms of input fields to capture the information (email address, password, etc.) of a user and the implementation of the onSubmit class method when a user signs up eventually.
First, letâs initialize the state of the component. It will capture the user information such as username, email, and password. There will be a second password field/state for a password confirmation. In addition, there is an error state to capture an error object in case of the sign up request to the Firebase API fails. The state is initialized by an object destructuring. This way, we can use the initial state object to reset the state after a successful sign up.
Letâs implement all the input fields to capture the information in the render method of the component. The input fields need to update the local state of the component by using a onChange handler.
Letâs take the last implemented code block apart. All the input fields implement the unidirectional data flow of React; thus, each input field gets a value from the local state and updates the value in the local state with a onChange handler. The input fields are controlled by the local state of the component and donât control their own states. They are controlled components .
In the last part of the form, there is an optional error message from an error object. The error objects from Firebase have this message property by default, so you can rely on it to display the proper text for your applicationâs user. However, the message is only shown when there is an actual error using a conditional rendering .
One piece in the form is missing: validation. Letâs use an isInvalid boolean to enable or disable the submit button.
The user is only allowed to sign up if both passwords are the same, and if the username, email and at least one password are filled with a string. This is password confirmation in a common sign up process.
You should be able to visit the /signup route in your browser after starting your application to confirm that the form with all its input fields shows up. You should also be able to type into it (confirmation that the local state updates are working) and able to enable the submit button by providing all input fields a string (confirmation that the validation works).
Whatâs missing in the component is the onSubmit() class method, which will pass all the form data to the Firebase authentication API via your authentication interface in the Firebase class:
The code is not working yet, but letâs break down what we have so far. All the necessary information passed to the authentication API can be destructured from the local state. You will only need one password property, because both password strings should be the same after the validation.
Next, call the sign up function defined in the previous section in the Firebase class, which takes the email and the password property. The username is not used yet for the sign up process, but will be used later.
If the request resolves successfully, you can set the local state of the component to its initial state to empty the input fields. If the request is rejected, you will run into the catch block and set the error object in the local state. An error message should show up in the form due to the conditional rendering in your componentâs render method.
Also, the preventDefault() method on the event prevents a reload of the browser which otherwise would be a natural behavior when using a submit in a form. Note that the signed up user object from the Firebase API is available in the callback function of the then block in our request. You will use it later with the username.
You may have also noticed that one essential piece is missing: We didnât make the Firebase instance available in the SignUpForm componentâs props yet. Letâs change this by utilizing our Firebase Context in the SignUpPage component, and by passing the Firebase instance to the SignUpForm.
Now the registration of a new user should work. However, Iâd like to make one improvement on how we access the Firebase instance here. Rather than using a render prop component , which is automatically given with Reactâs Context Consumer component, it may be simpler to use a higher-order component . Letâs implement this higher-order component in the src/components/Firebase/context.js :
Next, make it available via our Firebase module in the src/components/Firebase/index.js file:
Now, instead of using the Firebase Context directly in the SignUpPage, which doesnât need to know about the Firebase instance, use the higher-order component to wrap your SignUpForm. Afterward, the SignUpForm has access to the Firebase instance via the higher-order component. Itâs also possible to use the SignUpForm as standalone without the SignUpPage, because it is responsible to get the Firebase instance via the higher-order component.
When a user signs up to your application, you want to redirect the user to another page. It could be the userâs home page, a protected route for only authenticated users. You will need the help of React Router to redirect the user after a successful sign up.
Letâs take the previous code block apart again. To redirect a user to another page programmatically, we need access to React Router to redirect the user to another page. Fortunately, the React Router node package offers a higher-order component to make the router properties accessible in the props of a component. Any component that goes in the withRouter() higher-order component gains access to all the properties of the router, so when passing the enhanced SignUpFormBase component to the withRouter() higher-order component, it has access to the props of the router. The relevant property from the router props is the history object, because it allows us to redirect a user to another page by pushing a route to it.
The history object of the router can be used in the onSubmit() class method eventually. If a request resolves successfully, you can push any route to the history object. Since the pushed /home route is defined in our App component with a matching component to be rendered, the displayed page component will change after the redirect.
There is one improvement that we can make for the higher-order components used for the SignUpForm. Nesting functions (higher-order components) into each other like we did before can become verbose. A better way is to compose the higher-order components instead. To do this, install recompose for your application on the command line:
You can use recompose to organize your higher-order components. Since the higher-order components donât depend on each other, the order doesnât matter. Otherwise, it may be good to know that the compose function applies the higher-order components from right to left.
Run your application again. If you signed up a user successfully, it should redirect to the home page. If the sign up fails, you should see an error message. Try to sign up a user with the same email address twice and verify that a similar error message shows up: âThe email address is already in use by another account.â. Congratulations, you signed up your first user via Firebase authentication.
A sign up automatically results in a sign in/login by the user. We cannot rely on this mechanic, however, since a user could be signed up but not signed in. Letâs implement the login with Firebase now. It is similar to the sign up mechanism and components, so this time we wonât split it into so many code blocks. Implement the src/components/SignIn/index.js file:
It is almost the same as the sign up form. Its input fields capture all the necessary information like username and password. A validation step makes sure the email and password are set before performing the request by enabling or disabling the submit button. The authentication API is used again, this time with a function to sign in the user rather than sign them up. If sign in succeeds, the local state is updated with the initial state and the user is redirected again. If the sign in fails, an error object is stored in the local state and an error message appears. The SignUpLink, which was defined earlier in the SignUp module, is used on the sign in page. It lets users sign up if they donât have an account, and it is found on the sign in page.
To complete the authentication loop, next weâll implement the sign out component. The component is just a button that appears within the Navigation component. Since we can use the previously-defined authentication API to sign out a user, passing functionality to a button in a React component is fairly straightforward. Implement the SignOutButton component in the src/components/SignOut/index.js file:
The SignOutButton has access to the Firebase instance using the higher-order component again. Now, use the SignOutButton in the Navigation component in your src/components/Navigation/index.js file:
Regarding components, everything is set to fulfil a full authentication roundtrip. Users can sign up (register), sign in (login), and sign out (logout).
This section is the most important one for the authentication process. You have all the components needed to fulfil an authentication roundtrip in React, and all thatâs missing is an overseer for the session state. Logic regarding the current authenticated user needs to be stored and made accessible to other components. This is often the point where developers start to use a state management library like Redux or MobX . Without these, weâll make due using global state instead of state management libraries.
Since our application is made under the umbrella of App component, itâs sufficient to manage the session state in the App component using Reactâs local state. The App component only needs to keep track of an authenticated user (session). If a user is authenticated, store it in the local state and pass the authenticated user object down to all components that are interested in it. Otherwise, pass the authenticated user down as null . That way, all components interested in it can adjust their behavior (e.g. use conditional rendering) based on the session state. For instance, the Navigation component is interested because it has to show different options to authenticated and non authenticated users. The SignOut component shouldnât show up for a non authenticated user, for example.
We handle session handling in the App component in the src/components/App/index.js file. Because the component handles local state now, you have to refactor it to a class component. It manages the local state of a authUser object, and then passes it to the Navigation component.
The Navigation component can be made aware of authenticated user to display different options. It should either show the available links for an authenticated user or a non authenticated user.
Letâs see where the authUser (authenticated user) comes from in the App component. Firebase offers a listener function to get the authenticated user from Firebase:
The helper function onAuthStateChanged() receives a function as parameter that has access to the authenticated user. Also, the passed function is called every time something changes for the authenticated user. It is called when a user signs up, signs in, and signs out. If a user signs out, the authUser object becomes null, so the authUser property in the local state is set to null and all components depending on it adjust their behavior (e.g. display different options like the Navigation component).
We also want to avoid memory leaks that lead to performance issues , so weâll remove the listener if the component unmounts.
Start your application and verify that your sign up, sign in, and sign out functionality works, and that the Navigation component displays the options depending on the session state (authenticated user).
Congratulations, you have successfully implemented the authentication process with Firebase in React. Everything in the following sections regarding authentication is considered extra, to improve the developerâs experience and add a couple of useful features along the way.
We added a basic version of session handling in the last section. However, the authenticated user still needs to be passed down from the App component to interested parties. That can become tedious over time, because the authenticated user has to be passed through all components until it reaches all the leaf components. You used the React Context API to pass down the Firebase instance to any component before. Here, you will do the same for the authenticated user. In a new src/components/Session/context.js file, place the following new React Context for the session (authenticated user):
Next, import and export it from the src/components/Session/index.js file that is the entry point to this module:
The App component can use the new context to provide the authenticated user to components that are interested in it:
The authUser doesnât need to be passed to the Navigation component anymore. Instead, the Navigation component uses the new context to consume the authenticated user:
The application works the same as before, except any component can simply use Reactâs Context to consume the authenticated user. To keep the App component clean and concise, I like to extract the session handling for the authenticated user to a separate higher-order component in a new src/components/Session/withAuthentication.js file:
Move all logic that deals with the authenticated user from the App component to it:
As you can see, it also uses the new React Context to provide the authenticated user. The App component will not be in charge of it anymore. Next, export the higher-order component from the src/components/Session/index.js file, so that it can be used in the App component after:
The App component becomes a function component again, without the additional business logic for the authenticated user. Now, it uses the higher-order component to make the authenticated user available for all other components below of the App component:
Start the application and verify that it still works. You didnât change any behavior in this section, but shielded away the more complex logic into a higher-order component. Also, the application now passes the authenticated user implicitly via Reactâs Context, rather than explicitly through the component tree using props.
Letâs take a step back from the higher-order components, React Context API, and session handling. In this section, we will implement two additional features available in the Firebase authentication API, the ability to retrieve (password forget) and change a password.
Letâs start by implementing the password forget feature. Since you already implemented the interface in your Firebase class, you can use it in components. The following file adds most of the password reset logic in a form again. We already used a couple of those forms before, so it shouldnât be different now. Add this in the src/components/PasswordForget/index.js file:
The code is verbose, but it itâs no different from the sign up and sign in forms from previous sections. The password forget uses a form to submit the information (email address) needed by the Firebase authentication API to reset the password. A class method (onSubmit) ensures the information is send to the API. It also resets the formâs input field on a successful request, and shows an error on an erroneous request. The form is validated before it is submitted as well. The file implements a password forget link as a component which isnât used directly in the form component. It is similar to the SignUpLink component that we used on in the SignInPage component. This link is the same, and itâs still usable. If a user forgets the password after sign up, the password forget page uses the link in the src/components/SignIn/index.js file:
The password forget page is already matched in the App component, so you can drop the PasswordForgetLink component in the sign in page and know the mapping between route and component is complete. Start the application and reset your password. It doesnât matter if you are authenticated or not. Once you send the request, you should get an email from Firebase to update your password.
Next weâll add the password change feature, which is also in your Firebase interface. You only need a form component to use it. Again, the form component isnât any different from the sign in, sign up, and password forget forms. In the src/components/PasswordChange/index.js file add the following component:
The component updates its local state using onChange handlers in the input fields. It validates the state before submitting a request to change the password by enabling or disabling the submit button, and it shows again an error message when a request fails.
So far, the PasswordChangeForm is not matched by any route, because it should live on the Account page. The Account page could serve as the central place for users to manage their account, where it shows the PasswordChangeForm and PasswordResetForm, accessible by a standalone route. You already created the src/components/Account/index.js file and matched the route in the App component. You only need to implement it:
The Account page doesnât have any business logic. It uses the password forget and password change forms in a central place. In this section, your user experience improved significantly with the password forget and password change features, handling scenarios where users have trouble remembering passwords.
So far, all of your applicationâs routes are accessible by everyone. It doesnât matter whether the user is authenticated or not authenticated. For instance, when you sign out on the home or account page, there is no redirect, even though these pages should be only accessible for authenticated users. There is no reason to show a non authenticated user the account or home page in the first place, because these are the places where a user accesses sensitive information. In this section, so you will implement a protection for these routes called authorization. The protection is a broad-grained authorization , which checks for authenticated users. If none is present, it redirects from a protected to a public route; else, it will do nothing. The condition is defined as:
In contrast, a more fine-grained authorization could be a role-based or permission-based authorization:
Fortunately, we implement it in a way that lets you define the authorization condition (predicate) with flexibility, so that you can use a more generalized authorization rule, permission-based or role-based authorizations.
Like the withAuthentication higher-order component, there is a withAuthorization higher-order component to shield the authorization business logic from your components. It can be used on any component that needs to be protected with authorization (e.g. home page, account page). Letâs start to add the higher-order component in a new src/components/Session/withAuthorization.js file:
So far, the higher-order component is not doing anything but taking a component as input and returning it as output. However, the higher-order component should be able to receive a condition function passed as parameter. You can decide if it should be a broad or fine-grained (role-based, permission-based) authorization rule. Second, it has to decide based on the condition whether it should redirect to a public page (public route), because the user isnât authorized to view the current protected page (protected route). Letâs paste the implementation details for the higher-order component and go through it step-by-step:
The render method displays the passed component (e.g. home page, account page) that should be protected by this higher-order component. We will refine this later. The real authorization logic happens in the componentDidMount() lifecycle method. Like the withAuthentication() higher-order component, it uses the Firebase listener to trigger a callback function every time the authenticated user changes. The authenticated user is either a authUser object or null . Within this function, the passed condition() function is executed with the authUser . If the authorization fails, for instance because the authenticated user is null , the higher-order component redirects to the sign in page. If it doesnât fail, the higher-order component does nothing and renders the passed component (e.g. home page, account page). To redirect a user, the higher-order component has access to the history object of the Router using the in-house withRouter() higher-order component from the React Router library.
Remember to export the higher-order component from your session module into the src/components/Sessions/index.js file:
In the next step, you can use the higher-order component to protect your routes (e.g. /home and /account) with authorization rules using the passed condition() function. To keep it simple, the following two components are only protected with a broad authorization rule that checks if the authUser is not null . First, enhance the HomePage component with the higher-order component and define the authorization condition for it:
Second, enhance the AccountPage component with the higher-order component and define the authorization condition. It similar to the previous usage:
The protection of both pages/routes is almost done. One refinement can be made in the withAuthorization higher-order component using the authenticated user from the context:
The improvement in the render method was needed to avoid showing the protected page before the redirect happens. You want to show nothing if the authenticated user doesnât meet the conditionâs criteria. Then itâs fine if the listener is too late to redirect the user, because the higher-order component didnât show the protected component.
Both routes are protected now, so we can render properties of the authenticated user in the AccountPage component without a null check for the authenticated user. You know the user should be there, otherwise the higher-order component would redirect to a public route.
You can try it by signing out from your application and trying to access the /account or /home routes. Both should redirect you to the /signin route. It should also redirect you automatically when you stay on one of the routes while you sign out.
You can imagine how this technique gives control over authorizations, not just by broader authorization rules, but more specific role-based and permission-based authorizations. For instance, an admin page available for users with the admin role could be protected as follows:
Donât worry about this yet, because weâll implement a role-based authorization for this application later. For now, you have successfully implemented a full-fledged authentication mechanisms with Firebase in React, added neat features such as password reset and password change, and protected routes with dynamic authorization conditions.
So far, only Firebase knows about your users. There is no way to retrieve a single user or a list of users for your application from their authentication database. They are stored internally by Firebase to keep the authentication secure. Thatâs good, because you are never involved in storing sensitive data like passwords. However, you can introduce the Firebase realtime database to keep track of user entities yourself. It makes sense, because then you can associate other domain entities (e.g. a message, a book, an invoice) created by your users to your users. You should keep control over your users, even though Firebase takes care about all the sensitive data. This section will explain how to store users in your realtime database in Firebase. First, initialize the realtime database API for your Firebase class as you did earlier for the authentication API:
Second, extend the interface for your Firebase class for the user entity. It defines two new functions: one to get a reference to a user by identifier (uid) and one to get a reference to all users:
The paths in the ref() method match the location where your entities (users) will be stored in Firebaseâs realtime database API. If you delete a user at âusers/5â, the user with the identifier 5 will be removed from the database. If you create a new user at âusersâ, Firebase creates the identifier for you and assigns all the information you pass for the user. The paths follow the REST philosophy where every entity (e.g. user, message, book, author) is associated with a URI, and HTTP methods are used to create, update, delete and get entities. In Firebase, the RESTful URI becomes a simple path, and the HTTP methods become Firebaseâs API.
Now, use these references in your React components to create and get users from Firebaseâs realtime database. The best place to add user creation is the SignUpForm component, as it is the most natural place to save users after signing up via the Firebase authentication API. Add another API request to create a user when the sign up is successful. In src/components/SignUp/index.js file:
There are two important things happening for a new sign up via the submit handler:
To create a user in Firebaseâs realtime database, it uses the previously created reference from the Firebase class by providing the identifier (uid) of the user from Firebaseâs authentication database. Then the set() method can be used to provide data for this entity which is allocated for âusers/uidâ. Finally, you can use the username as well to provide additional information about your user.
Note: It is fine to store user information in your own database. However, you should make sure not to store the password or any other sensitive data of the user on your own. Firebase already deals with the authentication, so there is no need to store the password in your database. Many steps are necessary to secure sensitive data (e.g. encryption), and it could be a security risk to perform it on your own.
After the second Firebase request that creates the user resolves successfully, the previous business logic takes place again: reset the local state and redirect to the home page. To verify the user creation is working, retrieve all the users from the realtime database in one of your other components. The admin page may be a good choice for it, because it can be used by admin users to manage the application-wide users later. First, make the admin page available via your Navigation component:
Next, the AdminPage componentâs componentDidMount() lifecycle method in src/components/Admin/index.js is the perfect place to fetch users from your Firebase realtime database API:
We are using the users reference from our Firebase class to attach a listener. The listener is called on() , which receives a type and a callback function. The on() method registers a continuous listener that triggers every time something has changed, the once() method registers a listener that would be called only once. In this scenario, we are interested to keep the latest list of users though.
Since the users are objects rather than lists when they are retrieved from the Firebase database, you have to restructure them as lists (arrays), which makes it easier to display them later:
Remember to remove the listener to avoid memory leaks from using the same reference with the off() method:
Render your list of users in the AdminPage component or in a child component. In this case, we are using a child component:
You have gained full control of your users now. It is possible to create and retrieve users from your realtime database. You can decide whether this is a one-time call to the Firebase realtime database, or if you want to continuously listen for updates as well.
Everything essential is in place for Firebase authentication and Firebase realtime database for user management. I am interested in seeing what you will build on top of it! If you want to continue to follow this tutorial, get the whole book to finish this application with plenty of powerful features.
Whatâs else will be included in the book?
Role-based Authorization: So far, you have only authorized your application on a broad level, by checking for an authenticated user. In the book, you will learn how to assign roles to your users and how to give them additional privileges.
User Management: In order to get more control over your users, I will show you how to merge authentication user and database user. Then you can always assign new properties to your database user while having access to it on your user after authentication too.
Users and Messages: Next to the user management, you will introduce a second entity for messages to your application. By using both entities, user and message, we can build a chat application.
Read and Write Operations: In the application, you created a user and display a list of users with real-time capabilities. The book continuous with the usual delete and update operations to organize your users in the realtime database.
Offline, Double Opt-In, Social Logins: The book adds more Firebase attributes ike offline capabilities, double opt-in sign ups, and social sign ups/ins via third-parties like Facebook or Google.
Firebase Deployment: The final step in the book is to deploy an application with Firebase. The books walks you through the process step-by-step to see your project online.
Firestore: Firebaseâs Firestore is the new Firebase Realtime Database. In the book, I may show you a way to migrate to this new tech stack. Then it is up to you whether you want to use Firestore or Firebaseâs Realtime Database.
Source Code Projects: This application is only build with React and Firebase. But what about taking it on the next level to enable it for real businesses? Thatâs where I want to show you how to migrate the project to Redux, MobX, or Gatsby.js. You will get access to variations of this application that will have additional tech when choosing the course instead of only the book: