DEV Community

loading...
Cover image for Firebase Google Sign in with React

Firebase Google Sign in with React

gathoni profile image Mary Gathoni ・6 min read

Introduction

Firebase Authentication provides an easy way to log in users using their already existing social accounts. In this tutorial, we will be looking at how to sign in a user using their google account.

What I'll show you

  • Set up a Firebase project.
  • Initializing a Firebase app.
  • Enabling user authentication in Firebase.
  • Using social providers to log in users
  • Using the Context API to provide the authentication status to child components.
  • Protecting routes i.e allowing only authenticated users to access a route.
  • Handling user logout

Requirements

Starter Code

Clone the starter code git clone -b starter https://github.com/gathoni-k/Firebase-Google-Signin.git

First off, head here to create a project

Click on get started
Firebase Get Started
You'll be taken to a projects page. Select Add Project and give your project any name you would like,you can accept google analytics or not and choose a Firebase account, You can choose the default account.
After a while, you'll be in the Project Overview Page.

Firebase add app

To add an app, just below the Get Started... text, click on the third icon, this will let you create a web app and give your web app a nickname..

Web app nickname

Click next and copy the firebaseConfig object, we will be using it later to initialize our app.

To enable authentication, Go back to your projects overview page and click on Authentication tab then set up sign-in method and enable Google
.
Set up sign in method

Okay cool now to the fun stuff...πŸ’ƒπŸΎπŸ’ƒπŸΎ

Lets start off by grabbing that Firebase config object we grabbed from earlier, since these is sensitive information, we will want to store in a .env file and add it to the .gitignore file so that we don't mistakenly push it to a version source control provider like GitHub.

Having said that, create a .env file in the root folder and add the following

.env

REACT_APP_API_KEY=""
REACT_APP_AUTH_DOMAIN=""
REACT_APP_DATABASE_URL=""
REACT_APP_PROJECT_ID=""
REACT_APP_STORAGE_BUCKET=""
REACT_APP_MESSAGING_SENDER_ID=""
REACT_APP_APP_ID=""
REACT_APP_MEASUREMENT_ID=""
Enter fullscreen mode Exit fullscreen mode

Now fill it in using the corresponding values from the Firebase config object we grabbed earlier.

Note that the REACT_APP_ prefix is necessary. You can read more on setting custom environment variables in react here

To access .env variables, we will have to install dotenv. This is an npm package that loads environment variables from a .env file into process.env.

yarn add dotenv
Enter fullscreen mode Exit fullscreen mode

Install Firebase

yarn add firebase
Enter fullscreen mode Exit fullscreen mode

Now lets put all that to use
Create a services folder and create firebase.js file in it and add the following code

src/services/firebase.js

import dotenv from 'dotenv'
dotenv.config()
import * as firebase from "firebase/app";
import "firebase/auth";

firebase.initializeApp({
  apiKey: process.env.REACT_APP_API_KEY,
  authDomain: process.env.REACT_APP_AUTH_DOMAIN,
  databaseURL: process.env.REACT_APP_DATABASE_URL,
  projectId: process.env.REACT_APP_PROJECT_ID,
  storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
  messagingSenderId:  process.env.REACT_APP_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_APP_ID,
  measurementId: process.env.REACT_APP_MEASUREMENT_ID
});
Enter fullscreen mode Exit fullscreen mode

In the above code:

  • we've imported dotenv and configured it.
  • Imported firebase
  • Initialized firebase app Next create a signin function that will let us sign in with google. We will use the signInWithPopup() method.

src/services/firebase.js

export const auth = firebase.auth();
const googleProvider = new firebase.auth.GoogleAuthProvider()
export const signInWithGoogle = () => {
  auth.signInWithPopup(googleProvider).then((res) => {
    console.log(res.user)
  }).catch((error) => {
    console.log(error.message)
  })
}
Enter fullscreen mode Exit fullscreen mode

To use this function, we will have to import it in the Login.js file and add an onClick handler to the sign in button.

src/Login.js

import React from "react";
import "./Login.css"
import { signInWithGoogle } from "./services/firebase";
export default function Login() {
  return (
      <div className="login-buttons">
        <button className="login-provider-button" onClick={signInWithGoogle}>
        <img src="https://img.icons8.com/ios-filled/50/000000/google-logo.png" alt="google icon"/>
        <span> Continue with Google</span>
       </button>
      </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And that's it, you can now sign in your user via their google account.
But how do you prevent unauthorized users from accessing protected routes? Well, they are several ways but I'll show you how by using the context API. The context API is a way of defining global variables that can be accessed through your components instead of passing props component to component(prop drilling).

Create a Providers folder and in it create a UserProvider.js file

src/providers/UserProvider.js

import React, {useState, useEffect,  createContext} from "react";
import { auth } from "../services/firebase"
export const UserContext = createContext({user: null})
export default () => {
  const [user, setuser] = useState(null)
  useEffect(() => {
auth.onAuthStateChanged(async (user) => {
  const { displayName, email }  = user;
  setuser({
    displayName,
    email
  })
})
  },[])
  return (
    <UserContext.Provider value={user}>{props.children}</UserContext.Provider>
  )
}
Enter fullscreen mode Exit fullscreen mode

To understand the above code, we first have to understand the context API.
We will store the user value as context, and so we create that using createContext() and pass in the initial value of our user, in this case null and assign in to a variable UserContext.
UserContext will give us the Provider component which provides values. In our case it will provide us with the user.
In the default function, we will have to keep track of the authentication status of our user. This is done using the onAuthStateChanged, a function provided by firebase.auth() which we exported in the firebase.js app as auth.
Once the user signs in the state is updated with their display name and email.
Finally, the function returns the UserContext Provider component with the user value.

To use these value we have to wrap the components we want to use the user value in with the the UserProvider component.

src/App.js

import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import "./App.css"
import Navbar from "./Navbar"
import Login from "./Login";
import Dashboard from "./Dashboard";
import UserProvider from "./providers/UserProvider";
function App() {
  return (
    <UserProvider>
    <Router>
    <Navbar/>
    <div className="App">
        <Switch>
          <Route exact path="/">
            <Login />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
        </Switch>
    </div>
    </Router>
    </UserProvider>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

The user value will now be available to our components via the useContext hook.

src/Login.js

import React, { useContext } from 'react';
import './Login.css'
import { signInWithGoogle } from './services/firebase';
import { UserContext } from './providers/UserProvider';
export default function Login() {
  const user = useContext(UserContext)
  return (
      <div className="login-buttons">
        <button className="login-provider-button" onClick={signInWithGoogle}>
        <img src="https://img.icons8.com/ios-filled/50/000000/google-logo.png" alt="google icon"/>
        <span> Continue with Google</span>
       </button>
      </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now what?
Now we check the user value and redirect to the dashboard page if they are authenticated

src/Login.js

import React, { useEffect, useContext, useState } from 'react';
import './Login.css'
import { signInWithGoogle } from './services/firebase';
import { UserContext } from './providers/UserProvider';
import { Redirect } from 'react-router-dom';
export default function Login() {
  const user = useContext(UserContext)
  const [redirect, setredirect] = useState(null)

  useEffect(() => {
    if (user) {
      setredirect('/dashboard')
    }
  }, [user])
  if (redirect) {
    <Redirect to={redirect}/>
  }
  return (
      <div className="login-buttons">
        <button className="login-provider-button" onClick={signInWithGoogle}>
        <img src="https://img.icons8.com/ios-filled/50/000000/google-logo.png" alt="google icon"/>
        <span> Continue with Google</span>
       </button>
      </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

So there are few new things here. First, the redirect state and the useEffect hook.
The useEffect hook will run after the component renders. It checks for the user value and if its there, that means the user is authenticated and can be redirected to the dashboard.
By setting redirect to the dashboard path, we redirect the user appropriately. Neat!

One problem though, if a user goes to /dashboard, they will still have access. We really don't want that.
To protect the dashboard route, we have to check for the user's authentication status, if authenticated then cool they can stay, if not then we kick them out by redirecting the to the login page

src/DashBoard.js

import React from "react";
import "./Dashboard.css";
import React, { useEffect, useContext, useState } from "react";
import { UserContext } from "./providers/UserProvider";
import { Redirect } from "react-router-dom";
export default function Dashboard() {
  const user = useContext(UserContext);
  const [redirect, setredirect] = useState(null);

  useEffect(() => {
    if (!user) {
      setredirect("/");
    }
  }, [user]);
  if (redirect) {
    <Redirect to={redirect} />;
  }
  return (
    <div className="dashboard">
      <h1 className="dashboard-text">Welcome Home</h1>
      <button className="logout-button">
        <img
          src="https://img.icons8.com/ios-filled/50/000000/google-logo.png"
          alt="google icon"
        />
        <span> logout</span>
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now to logout, this is so simple. We just have to call auth.signOut() in our firebase.js file, import the function and add an onClick handler to the logout button.

src/services/firebase.js

...
export const logOut = () => {
  auth.signOut().then(()=> {
    console.log('logged out')
  }).catch((error) => {
    console.log(error.message)
  })
}
Enter fullscreen mode Exit fullscreen mode

src/Dashboard.js

import React from "react";
import "./Dashboard.css";
import React, { useEffect, useContext, useState } from "react";
import { UserContext } from "./providers/UserProvider";
import { Redirect } from "react-router-dom";
import { logOut } from "./services/firebase";
export default function Dashboard() {
  const user = useContext(UserContext);
  const [redirect, setredirect] = useState(null);

  useEffect(() => {
    if (!user) {
      setredirect("/");
    }
  }, [user]);
  if (redirect) {
    <Redirect to={redirect} />;
  }
  return (
    <div className="dashboard">
      <h1 className="dashboard-text">Welcome Home</h1>
      <button className="logout-button" onClick={logOut}>
        <img
          src="https://img.icons8.com/ios-filled/50/000000/google-logo.png"
          alt="google icon"
        />
        <span> logout</span>
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This tutorial was a bit lengthy but that's it.

Thanks for reading πŸ₯°

Discussion (10)

pic
Editor guide
Collapse
thebenforce profile image
Ben Force

Very well written, thanks! I'm pretty new at React, so learning about useContext was an added bonus.

Collapse
gathoni profile image
Mary Gathoni Author

You're welcome 😊

Collapse
imabigfan profile image
imabigfan

Hi Mary,
I am trying to use your code for a few days. Keep getting the following message:
Unhandled Rejection (TypeError): Cannot destructure property 'displayName' of 'user' as it is null.
Getting pretty frustrated trying to solve it.

Can you please... help me?

Thread Thread
gavranha profile image
Jocimar Lopes

You can use a condition to check IF user is there. Something like this:

useEffect(() => {
    auth.onAuthStateChanged(async (user) => {
        if(user){
            const { displayName, email } = user;
            setUser({
                displayName,
                email,
            });
        }
    });
}, []);
Enter fullscreen mode Exit fullscreen mode
Collapse
gavranha profile image
Jocimar Lopes • Edited

Hello, Mary, I've been looking for a tuto like this for two days. Have found several pro devs guides and none of them managed to put things in such a clean way.

To be honest, I'm a newbie with React, have little experience with ES6 and some years as a Java developer. So, I was looking for something that I could not only copy/paste. I was looking for a guide in which I could understand the logic of hooks and Context Providers applied to Firebase authentication.

Your write up is awesome, because I can debug it, if needed. And it is short. And it is clean :)

Thank you very much and keep up with the good job.

Collapse
gavranha profile image
Jocimar Lopes

If you got this error:
Attempted import error: 'auth' is not exported from 'firebase/app'

try this import:
import firebase from "firebase/app";
require("firebase/auth");

more info here
github.com/firebase/firebaseui-web...

Collapse
1akshat profile image
Akki • Edited

What if I want a user to redirect to login page on doing logout?
Is this a good idea?

import { Redirect } from "react-router-dom";
export const logOut = () => {
auth
.signOut()
.then(() => {
console.log("logged out..");
return ;
})
.catch((error) => {
console.warn(error.message);
});
};

Collapse
lethal254 profile image
lethal254

Thanks for this. I am an upcoming Kenyan developer too and am glad to see we are well represented.

Collapse
kdfriedman profile image
Kevin

Brilliantly written, well done!

Collapse
gramsco profile image
Antonin

Perfect, thanks !