Firebase Authentication and Realtime Chat in React

Firebase is Google’s backend-as-a-service platform, bundling authentication, a realtime NoSQL database, cloud functions, static hosting, and cloud storage into one SDK. For front-end developers, the main draw is that it removes server-side work: you configure a project in the Firebase console and call methods from the client. This article walks through building "Chatty," a React chat app that only lets authenticated users read and send messages, with sign-up via email and password, Google, or GitHub.

Firebase’s free tier covers email/password, Google, and GitHub authentication, and the Realtime Database permits up to 100 simultaneous connections and 1 GB of storage per month. Full pricing details are on the Firebase site. The complete source code for this project is available on GitHub.

Project Setup in the Firebase Console

  1. Go to the Firebase Console and click Add project, then enter a name (e.g., "Chatty"). Analytics is optional.
  2. After Firebase provisions resources, click the web icon to register a web app.
  3. Enter an app name, then copy the configuration details shown—you’ll need them shortly.

Next, enable the authentication providers from the Sign-in method tab: toggle Email/Password to enabled. You’ll do the same later for Google and GitHub.

Bootstrapping the React Client

With Node.js installed, use create-react-app to scaffold the project, then add React Router (for navigating between views) and the Firebase SDK:

npx create-react-app chatty
cd chatty
npm install react-router-dom firebase

Start the dev server with npm start to confirm the default screen renders. The final project structure keeps reusable pieces in src/components, src/helpers (auth and other utility functions), src/pages (app views), and src/services (Firebase).

Create src/services/firebase.js to initialize the SDK with the config from the console and export the auth and database modules for use throughout the app.

Routing with Authentication State

React has no built-in way to check whether a user is signed in, so we create two higher-order components (HOCs) to gate our routes. Both wrap a <Route>, pass router props through, and redirect when the auth condition is not met.

<PrivateRoute> accepts component, authenticated, and the remaining router props (via spread). If authenticated is true it renders the component; otherwise it redirects to /login. <PublicRoute> does the reverse, rendering public pages but bouncing to /chat once the user is authenticated.

In the root component, initialize state with loading: true and authenticated: false. In componentDidMount, call Firebase’s onAuthStateChanged method, which fires whenever the auth state changes. The callback receives a user object that is null when signed out; set the component state accordingly and flip loading to false. While loading is true, show a spinner; otherwise render the routing tree with our three public routes (<Home>, <Login>, <Signup>) and one private one (<Chat>).

Email and Password Registration and Login

In src/helpers/auth.js, import the auth module from the Firebase service and export two methods. signup(email, password) creates a new user with createUserWithEmailAndPassword. signin(email, password) logs in an existing user via signInWithEmailAndPassword.

The sign-up page is a controlled form: its initial state holds email, password, and an error string. A handleChange method uses computed property names to update the matching state key, and handleSubmit prevents the browser’s default form submission, clears any previous error, and calls the signup helper. On success, the onAuthStateChanged listener updates the app-level auth state, which triggers a redirect to /chat via the HOCs. On failure, the error message is stored in state and rendered to the user. The login page follows the same pattern but calls signin.

Google Sign-In

Back in the authentication tab, enable Google as a provider. On that page you’ll also see an authorized domains list; add localhost (the default is included for development) to avoid requests from unapproved origins.

Back in helpers/auth.js, add a signInWithGoogle function. Create an instance of GoogleAuthProvider, then pass it to signInWithPopup. Calling this opens a pop-up for the Google account flow; on success the same auth-state listener redirects the user to the chat view. Import the method in Signup.js, render a button that triggers it, and remember to bind the onClick handler in the constructor.

GitHub Sign-In

Enable GitHub in the Firebase dashboard. An authorization callback URL will be displayed; copy it before closing the panel. Register a new OAuth application on GitHub’s developer settings and paste that callback URL in. GitHub responds with a client ID and client secret, which go into the corresponding fields in the Firebase console.

In the auth helpers, add signInWithGitHub, mirroring the Google function but with GithubAuthProvider. In the signup page, import it, add a GitHub button, attach an onClick handler, and bind that handler as well.

Reading and Writing Messages

This project uses Firebase’s Realtime Database, a NoSQL store structured as key-value pairs. Add a chats node at the root; each child record contains content, timestamp, and a user ID.

Data permissions are set under the Rules tab. For this chat, the rule set should only permit authenticated users to read and write the chats node.

To read messages, import database from the services file. In componentDidMount, create a reference with db.ref("chats") and attach a .on("value") listener. This is the realtime part: the listener fires anew each time a record is added, returning an object that we loop through and convert to an array. Set that array to the chats state; on error, store the message in readError. Rendering is straight iteration over the chats array, showing each message and the sender.

Writing a message uses a form with an input bound to content in state. After preventing the default submit, clear any write error, then call push() on the database reference to create a unique key for the new object, writing the content, timestamp, and current user’s email. Since the client keeps its .on() connection open, the new entry appears for every connected user without a page reload.

Putting It Together

At this point you have a working chat app supporting email/password, Google, and GitHub authentication. The reusable auth logic in helpers/auth.js—the sign-up, sign-in, and pop-up methods—is useful beyond this demo, and the same patterns transler directly to other React projects where you need to keep certain routes private and others public. The full implementation is available in the linked repository.