DEV Community

Cover image for React authentication with Firebase
Dawx
Dawx

Posted on • Edited on

React authentication with Firebase

Hello everyone, in this guide I will show you how to set up basic authentication in React with Firebase. We will be also using react-router for creating routes (public and protected) and Redux ToolKit for saving user tokens to our applications state.

Project setup

First, we need to install React
npx create-react-app react-firebase
After it is installed we need to install dependencies we will use throughout this guide:

  • React Router DOM: npm install react-router-dom
  • Firebase: npm install firebase
  • Redux and Redux Toolkit: npm install react-redux and npm install @reduxjs/toolkit

After everything is installed you can start the local server:
cd react-firebase
npm start
If everything is alright you will get this screen:

React boilerplate start page

Project structure

In the src folder, we will create four new folders (configs, pages, redux, and utils). Configs will contain configuration for Firebase. Pages will contain all our pages, I also created a subfolder auth which will contain all pages regarding user authentication. Redux folder will container redux store and slices. Utils folder is for utilities like protected route components.

Project structure

Creating pages and routes

In pages->auth we will create 3 pages: Register, Login, Reset (password reset). I also created a folder „protected“ which has a page for authenticated users and a Home page for every user.

Pages folder

Login page

Below you can see basic React code for Login, it has two controlled inputs.



import React, { useState } from "react";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const handleLogin = () => {
    //here will go code for sign in
  };
  return (
    <div>
      <h1>Login</h1>
      Email:
      <br />
      <input
        type="text"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <br />
      Password:
      <br />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <br />
      <button onClick={handleLogin}>Log In</button>
    </div>
  );
};

export default Login;


Enter fullscreen mode Exit fullscreen mode

Register



import React, { useState } from "react";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const handleRegister = () => {
    //here will go code for sign up
  };
  return (
    <div>
      <h1>Register</h1>
      Email:
      <br />
      <input
        type="text"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <br />
      Password:
      <br />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <br />
      <button onClick={handleRegister}>Register</button>
    </div>
  );
};

export default Login;



Enter fullscreen mode Exit fullscreen mode

Password reset



import React, { useState } from "react";

const Reset = () => {
  const [email, setEmail] = useState("");
  const handleReset = () => {
    //here will go code for password reset
  };
  return (
    <div>
      <h1>Reset password</h1>
      Email:
      <br />
      <input
        type="text"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <br />
      <button onClick={handleReset}>Reset password</button>
    </div>
  );
};

export default Reset;



Enter fullscreen mode Exit fullscreen mode

The next step is to create links and routes for pages that will be in the App.js file. We can delete boilerplate code in App.js and write our own code.



import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Login from "./pages/auth/Login";
import Register from "./pages/auth/Register";
import Reset from "./pages/auth/Reset";
import Home from "./pages/Home";
import Secret from "./pages/protected/Secret";

function App() {
  return (
    <Router>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/login">Login</Link>
          </li>
          <li>
            <Link to="/register">Register</Link>
          </li>
          <li>
            <Link to="/reset">Reset password</Link>
          </li>
          <li>
            <Link to="/protected">Protected page</Link>
          </li>
          <li>
            <Link to="#">Log out</Link>
          </li>
        </ul>
      </nav>

      <Switch>
        <Route exact path="/register">
          <Register />
        </Route>
        <Route exact path="/login">
          <Login />
        </Route>
        <Route exact path="/reset">
          <Reset />
        </Route>
        <Route exact path="/protected">
          <Secret />
        </Route>
        <Route exact path="/">
          <Home />
        </Route>
      </Switch>
    </Router>
  );
}

export default App;


Enter fullscreen mode Exit fullscreen mode

First, we import react-router-dom dependencies and pages we just created. Then put <Router> as the root component. Under that is created basic navigation, instead of <a> element is used <Link> which doesn't refresh page on click (that's the point of Single Page Applications). Under navigation is a switch where we declare routes and components which they render. Now our screen looks something like this:

New homepage

The home page component is rendered at localhost:3000, if you click on the link in the navigation, other components will load without refreshing the page. Only Log Out doesn't render anything since it will be just used to log out.

Setting up Firebase

First, you need to create a Firebase account at https://firebase.google.com/ and go to the Firebase console at https://console.firebase.google.com. Click on „Add project“ and follow three simple steps.

Add project page

After you finished with three steps you will be redirected to the screen which looks like on the picture below. Click on the </> icon to generate code for the web app.

Get started with app -page

Then enter the name of the app:

Enter name of the app - page

And then you get configuration for your app!

App configuration

Now you can go to our project and in the config folder create file firebaseConfig.js. Paste config object and export it.

firebaseConfig.js file

After creating the config it's time to initialize Firebase in our project, we do this in App.js. First, we need to import config from our file and initializeApp from firebase, then at the top of our component, we initialize it.

App.js code snippet

There is one last thing to do. We need to enable email and password authentication in the Firebase console. To do that go to your project, press on the „Authentication“ link on the left sidebar, then „Set up sign-in method“ in the middle of the screen. Click on email and password, enable it, and save.

Firebase authentication page

With this Firebase setup is finished. In the next part, we will integrate existing forms in our project with Firebase to actually register, login, and log out users, as well as send password reset links.

Finish registration, login, log out and password reset

Registration

To register a user we need to import getAuth and createUserWithEmailAndPassword from firebase. getAuth gets us an instance of initialized auth service.



import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";


Enter fullscreen mode Exit fullscreen mode

Now we can declare variable auth which will hold auth service. Next, we can use „createUserWithEmailAndPassword“ in our handleRegister, first argument is auth service, then email, and lastly password. We create a promise if registration is successful you will get the user object logged in the console, if it wasn't successful, an error will be logged.



const auth = getAuth();
  const handleRegister = () => {
    createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        const user = userCredential.user;
        console.log("Registered user: ", user);
        setEmail("");
        setPassword("");
      })
      .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        console.log("Error ocured: ", errorCode, errorMessage);
      });
  };



Enter fullscreen mode Exit fullscreen mode

Here you can see the user object in the console after successful register:

Successful register

Login

For the login page, we do the same, but this time we are using „signInWithEmailAndPassword“. Just like last time, import getAuth and this time signInWithEmailAndPassword. Here is a code snippet of signIn handler.



const signIn = () => {
    signInWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        const user = userCredential.user;
        console.log("Singed in user: ", user);
      })
      .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        console.log("An error occured: ", errorCode, errorMessage);
      });
  };


Enter fullscreen mode Exit fullscreen mode

Password reset

Repeat the steps for a password reset, but this time use sendPasswordResetEmail. This method only requires email. Here is a code snippet.



const handleReset = () => {
    sendPasswordResetEmail(auth, email)
      .then(() => {
        console.log("success");
      })
      .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        console.log("An error has occured: ", errorCode, errorMessage);
      });
  };


Enter fullscreen mode Exit fullscreen mode

If it's successful you will get an email which will send you to page like this:

Firebase password reset

Log out

Since our navigation is directly in App.js, this is where we will implement log-out functionality. First import getAuth and signOut. Then add the following code to the „Log out“ link.



<Link
  to="#"
  onClick={() => {
    signOut(auth)
      .then(() => {
        console.log("user signed out");
      })
      .catch((error) => {
        console.log("error", error);
      });
  }}
>
  Log out
</Link>


Enter fullscreen mode Exit fullscreen mode

Setting up Redux Toolkit

In redux->slices folder create file authSlice.js. This file will save user in a global state and there will also be defined methods to manipulate with the state. Here is a code snippet:



import { createSlice } from "@reduxjs/toolkit";

const initialState = {};

export const authSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    saveUser: (state, action) => {
      state.value = action.payload;
    },
  },
});

// Action creators are generated for each case reducer function
export const { saveUser } = authSlice.actions;

export default authSlice.reducer;


Enter fullscreen mode Exit fullscreen mode

First, we import createSlice from RTK. Then initialize state as an empty object, next we create authSlice which has the name „user“, it has an initial state which is the empty object, and one reducer „saveUser“. saveUser takes two arguments, the first is a state which this slice has and the second is the action that will trigger it. It sets the value of the state to the payload of action (what you pass as an argument to that action). Lastly, we export saveUser and authSlice.

The next step is to set up a store which will hold state. In the root of redux folder create store.js file.



import { configureStore } from "@reduxjs/toolkit";
import authReducer from "./slice/authSlice";
export const store = configureStore({
  reducer: {
    auth: authReducer,
  },
});


Enter fullscreen mode Exit fullscreen mode

Here we configure the store with one auth reducer. This is how your redux folder structure should look like now:

redux folder structure

Now we need to provide a state from Redux to our app, to do that we need to wrap our component in index.js with the provider from redux which will use our store configuration.



import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { store } from "./redux/store";
import { Provider } from "react-redux";
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);


Enter fullscreen mode Exit fullscreen mode

The next step is to save the user token from firebase to our global state and update it every time something happens to the user. For that we will use onAuthStateChanged hook from firebase, every time auth changes we will save new user data to our global state. If there is no user, we just set a user to undefined.



import { getAuth, signOut, onAuthStateChanged } from "firebase/auth";
import { useSelector, useDispatch } from "react-redux";
import { saveUser } from "./redux/slice/authSlice";

function App() {
  initializeApp(firebaseConfig);
  const auth = getAuth();
  const user = useSelector((state) => state.auth.value);
  console.log("user from state", user);
  const dispatch = useDispatch();
  useEffect(() => {
    onAuthStateChanged(auth, (user) => {
      if (user) {
        dispatch(saveUser(user.refreshToken));
      } else {
        dispatch(saveUser(undefined));
      }
    });
  }, [auth, dispatch]);


Enter fullscreen mode Exit fullscreen mode

Now if you go log in and log out, you will see this in your console:

console logs

This is is it for this part. In the next part, we will set up protected routes which will be accessible only for logged in users.

Protected routes

Credit to @medaminefh and his article https://dev.to/medaminefh/protect-your-components-with-react-router-4hf7 where I took the code changed it a bit for this project.

In utils folder create file ProtectedRoute.js and paste this code in:



import React from "react";
import { Redirect, Route } from "react-router";
import { useSelector } from "react-redux";
const ProtectedRoute = ({ component: Component }) => {
  const user = useSelector((state) => state.auth.value);
  console.log("user", user);
  return (
    <Route
      render={(props) => {
        if (user) {
          return <Component {...props} />;
        } else {
          return <Redirect to="/" />;
        }
      }}
    />
  );
};

export default ProtectedRoute;


Enter fullscreen mode Exit fullscreen mode

ProtectedRoute takes in a component. First we „fetch“the user from the global state using useSelector hook, if the user exists provided component will render. Otherwise, the user will be redirected to the home page.

Now we can use the ProtectedRoute component in our App.js where routes are declared. First, import ProtectedRoute from utils and then simply replace which you want to protect with :



..
<Route exact path="/reset">
  <Reset />
</Route>

<ProtectedRoute exact path="/protected" component={Secret} />

<Route exact path="/">
  <Home />
</Route>
..


Enter fullscreen mode Exit fullscreen mode

Now if you are logged in, you will be able to see the protected component, else you will be redirected to the homepage.

This is it for this tutorial, if you have any questions, feel free to ask!

You can find this repository here: https://github.com/PDavor/react-firebase to make it working just add your firebase configuration!

Top comments (12)

Collapse
 
robin_beaudru_fd2d58ef438 profile image
Robin Beaudru

Thank you very much for your article!
I couldn't setup Firebase auth except with your tutorial!

How can I display, let's say the user email?
Cheers!

Collapse
 
dawx profile image
Dawx

Hi, you can get current users details with auth.currentUser, to display an email address you can do something like this: <p>{auth.currentUser.email}</p>

Hope this helps!

Collapse
 
robin_beaudru_fd2d58ef438 profile image
Robin Beaudru

Thank you I succeed exactly the same way!

I’m very new to programming, and even if login is a basic feature, it is really hard to learn !

I shared this tutorial to all of my boot camp colleagues!

Collapse
 
karthiknayak98 profile image
KarthikNayak

How to use Firebase Firestore with Redux-toolkit?

Collapse
 
dawx profile image
Dawx

Hi, unfortunately I'm not familiar with Firestore, this article is only about authentication which doesn't require Firestore, you can use any other database with this setup

Collapse
 
diegogonzalezcruz profile image
Diego G. Cruz

What do you wanna do?

Collapse
 
diegogonzalezcruz profile image
Diego G. Cruz

Hi, I'm using your useEffect code, and I get : Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

Collapse
 
dawx profile image
Dawx

Hi, if you tried to manually copy code from here, it is possible that you pasted it in wrong place, you can check repository and compare the difference github.com/PDavor/react-firebase

Collapse
 
diegogonzalezcruz profile image
Diego G. Cruz • Edited

Thanks, I fixed it!

Collapse
 
tuenguyen2911_67 profile image
Tue

This article helps! <3

Collapse
 
medaminefh profile image
Mohamed Amine Fh

Great Article ❤

Collapse
 
dawx profile image
Dawx

Thank you very much :)