Authentication with Firebase

In this chapter, we're going to allow users to sign in into our app. Firebase Authentication allows plenty of sign-in methods but for demonstration purposes we're going to use Google sign-in method.

Firebase Authentication integrates with other Firebase products, such as Firestore and Cloud Storage allowing you to limit user access to data using security rules. We going to explore this topic in next chapters.

Enabling sign-in method

First, you'll need to enable Google sign-in method at the providers tab in the Authentication section at the Firebase console.

After you fill in the required information and save the form, we'll be able to add authentication to our app.

Enabling the auth module

Before you can add auth to the app, you have to esnure that the Firebase auth module it's properly initialized. First of all, you need to import the firebase/auth module and set authDomain in the firebase.initializeApp call. Edit src/index.js:

import firebase from "firebase/app";
// 1. Ensure that you import the firebase/auth module
import "firebase/auth";
import "firebase/firestore";
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

firebase.initializeApp({
  apiKey: "AIzaSyCWG-cL-QJZ3OV2QVRForOMpIUZla1DepA",
  projectId: "tame-impala-chart",
  // 2. Set authDomain to YOUR_APP_ID.firebaseapp.com
  authDomain: "tame-impala-chart.firebaseapp.com"
});

ReactDOM.render(<App />, document.getElementById("root"));

serviceWorker.unregister();

Subscribing to authentication state

Now let's edit App component in src/App.js:

export default function App() {
  // 1. Create a state to store the user.
  // When the user's undefined, then the auth isn't initialized.
  // When it's null, then the user is not logged in.
  const [user, setUser] = useState();
  useEffect(() => {
    // 2. Add auth state change listener.
    firebase.auth().onAuthStateChanged(userData => {
      if (userData) {
        // 3. Save the user data to the state if it's logged in.
        setUser(userData);
      } else {
        // 4. Set the user to null to indicate that it's not logged in.
        setUser(null);
      }
    });
  }, []);

  const [songs, setSongs] = useState();
  useEffect(() => {
    return firebase
      .firestore()
      .collection("songs")
      .onSnapshot(songs => setSongs(songs.docs));
  }, []);

  if (user === undefined) {
    // 5. Display loading.
    return <div className="App">Loading...</div>;
  } else if (user === null) {
    // 6. Display the sign in button.
    return (
      <div className="App">
        <h1>Please sign in</h1>
        <button
          onClick={() => {
            // TODO: Add the sign in handler.
          }}
        >
          Sign in with Google
        </button>
      </div>
    );
  } else {
    return (
      <div className="App">
        <h1>My Favourite Tame Impala Songs</h1>

        {/* The rest of the App component... */}
      </div>
    );
  }
}

There's a lot going on so, let's review the code changes block by block.

First, we create a state to store the user and subscribe to the auth changes. When we receive an object, then it means that the user is signed in. When it's empty, then it's signed out.

const [user, setUser] = useState();
useEffect(() => {
  firebase.auth().onAuthStateChanged(userData => {
    if (userData) {
      setUser(userData);
    } else {
      setUser(null);
    }
  });
}, []);

Then depending on the user state we either render loading, if user is undefined (still loading), the sign in button if user is null (signed out) or the list of songs if user is present.


if (user === undefined) {
  return <div className="App">Loading...</div>;
} else if (user === null) {
  return (
    <div className="App">
      <h1>Please sign in</h1>
      <button>
        Sign in with Google
      </button>
    </div>
  );
} else {
  return (
    <div className="App">
      <h1>My Favourite Tame Impala Songs</h1>
      {/* The rest of the App component... */}
    </div>
  );
}

Signing in and out

Now, let's add the sign in button onClick handler.


<button
  onClick={() => {
    // 1. Create the Google auth provider
    const provider = new firebase.auth.GoogleAuthProvider();
    // 2. Sign in
    firebase.auth().signInWithPopup(provider);
  }}
>
  Sign in with Google
</button>

We used Google provider and then called signInWithPopup with the provider to open Google sign-in window in a popup.

It's time to add the last missing piece, the ability to sign out. Add a button to the beginning of the loaded state (before "My Favourite Tame Impala Songs" header):

<div className="App">
  <div>
    Signed in as {user.email} |{" "}
    <button
      onClick={() => {
        // Sign out the user
        firebase.auth().signOut();
      }}
    >
      Sign out
    </button>
  </div>

  <h1>My Favourite Tame Impala Songs</h1>

  {/* The rest of the component... */}
</div>

Now, users can sign in and sign out from their account:


We've added the ability to sign in and sign out using their Google account, but if you would sign in with two different users, you're going to see the same data. In the next chapter, we are going to explore how to model and query the data, so it's scoped to users.

Next chapter:
You'll learn how to use Firestore queries to filter and order data.