How to Set Up GitHub User Authentication using Firebase and React (with Hooks)

Adding user authentication is a key part of many modern web apps, but implementing it from scratch can be tricky, especially while also juggling the other aspects of frontend and backend development. Fortunately, Firebase makes it straightforward to add authentication to your app, with support for authenticating via Google, Facebook, Twitter, GitHub, email/password combinations, and more.

In this tutorial, we‘ll walk through how to allow users to log into your React app with their GitHub accounts using Firebase authentication. We‘ll also use modern React features like hooks and context to elegantly handle the authentication state in our app.

Here‘s an overview of what we‘ll cover:

  • Setting up a Firebase project and enabling GitHub authentication
  • Initializing Firebase in a React app
  • Creating a login page with a custom useLogin hook
  • Creating a logout button with a custom useLogout hook
  • Using the useContext and useReducer hooks to manage auth state
  • Conditionally rendering login/logout buttons and user profile info
  • Storing user data in Firestore on signup

Let‘s get started!

Setting up Firebase

First, we need to create a new Firebase project and set up GitHub authentication. Head over to the Firebase console and click "Add project". Give your project a name, then click through the remaining setup steps.

Once your project is created, go to the "Authentication" section in the sidebar and click the "Sign-in method" tab. Click on "GitHub" and toggle the switch to enable it. You‘ll need to provide the Client ID and Client secret from GitHub.

To get those, head over to the Register a new OAuth application page on GitHub. Fill in the form with your app‘s name, URL, and callback URL (you can find the callback URL back in the Firebase Authentication settings).

Once you register your app, GitHub will give you a Client ID and secret. Copy and paste those into the Firebase settings.

Initializing Firebase in React

Next, let‘s initialize Firebase in our React app. Install the Firebase SDK with:

npm install firebase

Then create a firebase.js file in your src directory with the following code:

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

const firebaseConfig = {
  // Your config here
};

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

export { auth, db };

Be sure to replace the firebaseConfig object with the actual config for your Firebase project, which you can find in the project settings.

Creating a useLogin Hook

Now let‘s create a custom useLogin hook that will handle logging in with GitHub. Create a useLogin.js file in your src directory with the following code:

import { useEffect, useState } from "react";
import { GithubAuthProvider, signInWithPopup } from "firebase/auth";
import { auth } from "./firebase";

export function useLogin() {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [user, setUser] = useState(null);

  async function login() {
    setIsLoading(true);
    try {
      const provider = new GithubAuthProvider();
      const { user } = await signInWithPopup(auth, provider);
      setUser(user);
    } catch (e) {
      setError(e);
    }
    setIsLoading(false);
  }

  return { isLoading, error, user, login };
}

This hook does a few things:

  1. It creates loading, error, and user state variables with useState
  2. It defines an async login function that initiates the GitHub login flow using signInWithPopup from the Firebase SDK
  3. If the login is successful, it sets the user state with the authenticated user object
  4. If there‘s an error, it sets the error state
  5. Finally, it returns an object with the state variables and login function

We can then use this hook in our login page component like so:

import { useLogin } from "./useLogin";

function LoginPage() {
  const { isLoading, error, user, login } = useLogin();

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  if (user) {
    return <p>Welcome {user.displayName}!</p>;
  }

  return <button onClick={login}>Login with GitHub</button>;
}

Creating a useLogout Hook

We can create a similar custom hook for handling logout. Create a useLogout.js file with the following:

import { signOut } from "firebase/auth";
import { auth } from "./firebase";

export function useLogout() {
  async function logout() {
    await signOut(auth);
  }

  return logout;
}

This hook simply wraps the signOut function from the Firebase SDK and returns it for use in our components. We can use it like:

import { useLogout } from "./useLogout";

function LogoutButton() {
  const logout = useLogout();
  return <button onClick={logout}>Logout</button>;
}

Managing Auth State with Context and useReducer

To make the auth state available throughout our app without prop drilling, we can use the Context API and useReducer hook.

First, create an AuthContext.js file:

import { createContext } from "react";

export const AuthContext = createContext();

Then create an authReducer.js file:

export function authReducer(state, action) {
  switch (action.type) {
    case "LOGIN":
      return { ...state, user: action.user };
    case "LOGOUT":
      return { ...state, user: null };
    default:
      return state;
  }
}

This reducer handles updating the auth state when the user logs in or out.

Now update your App.js to use the AuthContext and authReducer:

import { useReducer, useEffect } from "react";
import { onAuthStateChanged } from "firebase/auth";
import { AuthContext } from "./AuthContext";
import { authReducer } from "./authReducer";
import { auth } from "./firebase";

function App() {
  const [state, dispatch] = useReducer(authReducer, { user: null });

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      if (user) {
        dispatch({ type: "LOGIN", user });
      } else {
        dispatch({ type: "LOGOUT" });
      }
    });

    return () => unsubscribe();
  }, []);

  return (
    <AuthContext.Provider value={{ state, dispatch }}>
      {/* Your app here */}
    </AuthContext.Provider>
  );
}

This sets up the AuthContext with the initial auth state and a dispatch function to update it. It also uses the onAuthStateChanged observer from Firebase to dispatch LOGIN and LOGOUT actions when the auth state changes.

Now any component can access the auth state with the useContext hook:

import { useContext } from "react"; 
import { AuthContext } from "./AuthContext";

function MyComponent() {
  const { state } = useContext(AuthContext);
  // Use state.user here
}

Conditionally Rendering Based on Auth State

With the auth state available in the Context, we can easily conditionally render different parts of our app based on whether there is a currently logged-in user or not.

For example:

function NavBar() {
  const { state } = useContext(AuthContext);

  return (
    <nav>
      {state.user ? (
        <>
          <ProfileButton user={state.user} />
          <LogoutButton />
        </>
      ) : (
        <LoginButton />
      )}
    </nav>
  );
}

Storing User Data in Firestore

In addition to authenticating users, we can also store additional data about them in Firestore. A good place to do this is right after they log in or sign up.

Update the useLogin hook to store user data:

import { useState } from "react";
import { GithubAuthProvider, signInWithPopup } from "firebase/auth";
import { doc, getDoc, setDoc } from "firebase/firestore";
import { auth, db } from "./firebase";

export function useLogin() {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  async function login() {
    setIsLoading(true);
    try {
      const provider = new GithubAuthProvider();
      const { user } = await signInWithPopup(auth, provider);

      // Store user data in Firestore
      const userRef = doc(db, "users", user.uid);
      const userDoc = await getDoc(userRef);

      if (!userDoc.exists()) {
        await setDoc(userRef, {
          displayName: user.displayName,
          photoURL: user.photoURL,
          // Add any other data you want to store
        });
      }

    } catch (e) {
      setError(e);
    }
    setIsLoading(false);
  }

  return { isLoading, error, login };
}

Now whenever a new user logs in, their display name, profile photo URL, and any other info you specify will be stored in the users collection in Firestore. You can then access this data elsewhere in your app by querying the user document.

Conclusion

That covers the key steps for adding GitHub authentication to a React app with Firebase! We learned how to:

  • Set up a new Firebase project and enable GitHub authentication
  • Initialize Firebase in a React app
  • Create custom hooks for handling login and logout
  • Manage global auth state with the Context API and useReducer
  • Conditionally render parts of the app based on auth state
  • Store user data in Firestore on signup

I hope this in-depth tutorial has been helpful in adding robust authentication to your React apps. Let me know if you have any other questions!

Similar Posts