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
- At least node 8.10 and npm >= 5.6
- Basic understanding of react hooks
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
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.
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..
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
.
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=""
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
Install Firebase
yarn add firebase
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
});
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)
})
}
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>
);
}
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>
)
}
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;
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>
);
}
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>
);
}
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>
);
}
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)
})
}
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>
);
}
This tutorial was a bit lengthy but that's it.
Thanks for reading π₯°
Top comments (20)
Very well written, thanks! I'm pretty new at React, so learning about useContext was an added bonus.
You're welcome π
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?
You can use a condition to check IF user is there. Something like this:
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.
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...
I you're using "firebase": "^9.0.2", read this firebase.google.com/docs/web/modul...
Believe me! This is the best react context tutorial I've found. Thanks for sharing. ππΌ
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);
});
};
Its not Redirecting after logging in, can anybody tell
I think you have missed two things in the article one is while declaring the UserProvider.js "export default (props)"-props inside the brackets and secondly adding a return statement before redirect. Overall your article was pretty insightful .Keep up the good work buddy
I'll look into it and make changes. Thanks for pointing that out
Thanks for this. I am an upcoming Kenyan developer too and am glad to see we are well represented.
Perfect, thanks !
This is exactly what I was looking for, thank you.
I think:
should be: