Building Authentication in React Using Firebase

Building Authentication in React Using Firebase

Authentication is a crucial part of many web applications, ensuring that users have secure access to their accounts and data. Firebase, a cloud-based platform by Google, provides a convenient way to implement authentication in your React application. In this article, we'll guide you through building a basic authentication system using Firebase and React, while also emphasizing the importance of keeping your Firebase API keys secure.

Prerequisites

Before diving into the code, make sure you have the following set up:

  1. Node.js and npm (Node Package Manager) installed on your system.

  2. A Firebase project set up on the Firebase Console.

  3. Firebase CLI installed (npm install -g firebase-tools).

Setting Up Firebase

To securely access your Firebase project in your React app, you should not hardcode your Firebase API keys directly in your code, as they could be exposed in public repositories. Instead, you should store them in environment variables. Here's how you can do it:

  1. In your Firebase project settings, go to the "General" tab.

  2. Scroll down to the "Your apps" section and select the Firebase web app you've created.

  3. Under "Firebase SDK snippet," select the "Config" option. This will reveal your Firebase configuration object.

  4. Store the Firebase configuration object in an environment variable. You can use a .env file to manage your environment variables. Create a .env file in your project's root directory and add the following:

REACT_APP_FIREBASE_API_KEY=YOUR_API_KEY
REACT_APP_FIREBASE_AUTH_DOMAIN=YOUR_AUTH_DOMAIN
REACT_APP_FIREBASE_PROJECT_ID=YOUR_PROJECT_ID
REACT_APP_FIREBASE_STORAGE_BUCKET=YOUR_STORAGE_BUCKET
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=YOUR_MESSAGING_SENDER_ID
REACT_APP_FIREBASE_APP_ID=YOUR_APP_ID

Replace YOUR_API_KEY, YOUR_AUTH_DOMAIN, etc., with the values from your Firebase configuration object.

Installing Dependencies

To get started with React and Firebase authentication, install the necessary dependencies:

npm install react react-dom react-router-dom firebase

Creating the Firebase Configuration File

Now, let's create a Firebase configuration file to initialize Firebase in your React app. Create a file named firebaseConfig.js in your project's root directory with the following content:

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

export default auth;

This file initializes Firebase with the configuration stored in environment variables and exports the auth object for use in authentication-related components.

Building Authentication Components

Now, let's create the authentication components for login and sign-up.

Login Component

import { useRef, useState } from "react";
import { Link } from "react-router-dom";
import {
  sendPasswordResetEmail,
  signInWithEmailAndPassword,
} from "firebase/auth";
import auth from "../../firebase/firebaseConfig";

const Login = () => {
  const [loginError, setLoginError] = useState("");
  const [success, setSuccess] = useState("");
  const emailRef = useRef(null);

  const handleLogin = (e) => {
    e.preventDefault();
    const email = e.target.email.value;
    const password = e.target.password.value;

    setLoginError("");
    setSuccess("");

    // Attempt to sign in with email and password
    signInWithEmailAndPassword(auth, email, password)
      .then((result) => {
        // Check if the user's email is verified
        if (result.user.emailVerified) {
          setSuccess("Logged in Successfully");
        } else {
          alert("Please verify your email");
        }
      })
      .catch((error) => {
        setLoginError(error.message);
      });
  };

  const handleForgetPassword = () => {
    const email = emailRef.currentUser.value;
    if (!email) {
      console.log("Please provide an email", emailRef.current.value);
      return;
    } else if (
      !/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email)
    ) {
      console.log("Please write a valid email");
      return;
    }

    // Send a password reset email
    sendPasswordResetEmail(auth, email)
      .then(() => {
        alert("Please check your email for password reset instructions");
      })
      .catch((error) => {
        console.log(error);
      });
  };

  return (
    <div>
      {/* Login form */}
      <div className="hero min-h-screen bg-base-200">
        <div className="hero-content flex-col lg:flex-row-reverse">
          <div className="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
            <div className="card-body">
              <form onSubmit={handleLogin}>
                {/* Email input */}
                <div className="form-control">
                  <label className="label">
                    <span className="label-text">Email</span>
                  </label>
                  <input
                    type="text"
                    name="email"
                    placeholder="Email"
                    ref={emailRef}
                    className="input input-bordered"
                  />
                </div>

                {/* Password input */}
                <div className="form-control">
                  <label className="label">
                    <span className="label-text">Password</span>
                  </label>
                  <input
                    type="password"
                    name="password"
                    placeholder="Password"
                    className="input input-bordered"
                  />
                  <label className="label">
                    <a
                      onClick={handleForgetPassword}
                      href="#"
                      className="label-text-alt link link-hover"
                    >
                      Forgot password?
                    </a>
                  </label>
                </div>

                {/* Submit button */}
                <div className="form-control mt-6">
                  <button className="btn btn-primary">Login</button>
                </div>
              </form>

              {/* Display login error and success messages */}
              {loginError && <p className="text-red-700">{loginError}</p>}
              {success && <p className="text-green-600">{success}</p>}

              {/* Link to Sign-Up */}
              <p>
                New to this website? Please <Link to="/sign-up">Register</Link>
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Login;

Here's an explanation of the key parts of the Login component:

  1. useState: We use the useState hook to manage state for loginError (to store login errors) and success (to show a success message).

  2. emailRef: This is a useRef that allows us to access the email input field later.

  3. handleLogin: This function is called when the login form is submitted. It prevents the default form submission, retrieves the email and password from the form inputs, and attempts to sign in using Firebase's signInWithEmailAndPassword method. If successful, it checks if the user's email is verified. If not, it displays a message to verify the email. If there's an error, it sets the loginError state with the error message.

  4. handleForgetPassword: This function is called when the "Forgot password?" link is clicked. It retrieves the email from the input field, validates it, and sends a password reset email using Firebase's sendPasswordResetEmail method.

  5. Form Markup: The login form is defined with email and password inputs, and a "Forgot password?" link. The form submission triggers the handleLogin function.

  6. Display Error and Success Messages: We conditionally display error and success messages based on the state variables.

  7. Link to Sign-Up: We provide a link to the Sign-Up page for new users.

Sign-Up Component

import { useState } from "react";
import { Link } from "react-router-dom";
import {
  createUserWithEmailAndPassword,
  sendEmailVerification,
  updateProfile,
} from "firebase/auth";
import auth from "../../firebase/firebaseConfig";
import { FaEye, FaEyeSlash } from "react-icons/fa";

const Sign_Up = () => {
  const [error, setError] = useState("");
  const [success, setSuccess] = useState("");
  const [showPassword, setShowPassword] = useState(false);

  const handleSubmit = (e) => {
    e.preventDefault();
    const email = e.target.email.value;
    const password = e.target.password.value;
    const accepted = e.target.terms.checked;
    const name = e.target.name.value;

    setError("");

    // Validate password
    if (password.length < 6) {
      setError("Password must be at least 6 characters");
      return;
    } else if (!/[A-Z]/.test(password)) {
      setError("Your password should have at least one uppercase character.");
      return;
    } else if (!accepted) {
      setError("Please accept our terms and conditions!");
      return;
    }

    // Create a new user account with Firebase
    createUserWithEmailAndPassword(auth, email, password)
      .then((result) => {
        setSuccess("User Created Successfully.");

        // Update user profile with a display name
        updateProfile(result.user, {
          displayName: name,
        });

        // Send email verification
        sendEmailVerification(result.user).then(() => {
          alert("Please check your email and verify your account");
        });
      })
      .catch((error) => setError(error.message));
  };

  return (
    <div className="">
      <div className="mx-auto md:w-1/2">
        <h2 className="text-3xl mb-8">Please Register</h2>
        <form onSubmit={handleSubmit}>
          {/* Name input */}
          <input
            className="mb-4 w-full  py-

2 px-4"
            type="text"
            name="name"
            placeholder="Your Name"
            required
          />
          <br />
          {/* Email input */}
          <input
            className="mb-4 w-full  py-2 px-4"
            type="email"
            name="email"
            placeholder="Email Address"
            required
          />
          <br />
          {/* Password input */}
          <div className="mb-4 relative border">
            <input
              className="w-full py-2 px-4"
              type={showPassword ? "text" : "password"}
              name="password"
              placeholder="Password"
              required
            />
            <span
              className="absolute top-3 right-2"
              onClick={() => setShowPassword(!showPassword)}
            >
              {showPassword ? <FaEyeSlash></FaEyeSlash> : <FaEye></FaEye>}
            </span>
          </div>
          <br />
          {/* Terms and Conditions checkbox */}
          <div className="mb-2">
            <input type="checkbox" name="terms" id="terms" required />
            <label className="ml-2" htmlFor="terms">
              Accept our <a>Terms and Conditions</a>
            </label>
          </div>
          <br />
          {/* Submit button */}
          <input
            className="btn btn-secondary mb-4 w-full"
            type="submit"
            value="Register"
          />
        </form>

        {/* Display error and success messages */}
        {error && <p className="text-red-700">{error}</p>}
        {success && <p className="text-green-600">{success}</p>}

        {/* Link to Login */}
        <p>
          Already have an account? Please <Link to="/login">Login</Link>
        </p>
      </div>
    </div>
  );
};

export default Sign_Up;

Here's an explanation of the key parts of the Sign-Up component:

  1. useState: We use the useState hook to manage state for error (to store sign-up errors) and success (to show a success message).

  2. showPassword: This state variable is used to toggle the visibility of the password input.

  3. handleSubmit: This function is called when the sign-up form is submitted. It prevents the default form submission, retrieves user information (name, email, password, and terms acceptance), and validates the password. If everything is valid, it creates a new user account using Firebase's createUserWithEmailAndPassword method, updates the user's display name, and sends an email verification request using sendEmailVerification. If there's an error, it sets the error state with the error message.

  4. Form Markup: The sign-up form is defined with inputs for name, email, password, and a checkbox to accept terms and conditions. The form submission triggers the handleSubmit function.

  5. Display Error and Success Messages: We conditionally display error and success messages based on the state variables.

  6. Link to Login: We provide a link to the Login page for users who already have an account.

These components handle user authentication, validation, and error handling for login and sign-up functionalities in your React application using Firebase Authentication.

Setting Up Routing

Now, let's set up routing for our authentication components.

// router.js
import { createBrowserRouter } from "react-router-dom";
import App from "../App";
import Login from "../components/Login/Login";
import Sign_Up from "../components/Sign-Up/Sign-Up";

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      {
        path: "/login",
        element: <Login />,
      },
      {
        path: "/sign-up",
        element: <Sign_Up />,
      },
    ],
  },
]);

export default router;

Integrating Authentication into Your App

To integrate authentication into your app, import the router component into your app's entry point (e.g., index.js) and render it using React's ReactDOM.

// index.js
import React from "react";
import ReactDOM from "react-dom";
import { RouterProvider } from "react-router-dom";
import router from "./Route/Route";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

Protecting Firebase API Keys

Remember that you should never expose your Firebase API keys in public repositories or client-side code. By using environment variables as demonstrated in the firebaseConfig.js file, you can keep your keys safe and separate from your codebase.

Conclusion

In this article, we've covered the basic setup of Firebase authentication in a React application. We've created login and sign-up components, set up routing, and emphasized the importance of keeping your Firebase API keys secure using environment variables. With this foundation, you can build more advanced authentication features and enhance the security of your React application.

Follow me on : Github Linkedin