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:
- It creates loading, error, and user state variables with
useState
- It defines an async
login
function that initiates the GitHub login flow usingsignInWithPopup
from the Firebase SDK - If the login is successful, it sets the
user
state with the authenticated user object - If there‘s an error, it sets the
error
state - 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!