Quick links Set Up Firebase API React useContext() AuthContext Auth State Summary
This project will walk you through the steps to set up User Authentiaction via Google sign in using Firebase, React and React Context.
Authentication is what we call the process that allows a device to verify a user and then connect them with certain resources. Authentication is important because it allows us to keep our records secure. There are numerous ways to imnplement user Authentication and using Firebase just one of many options we can integrate in our apps.
Firebase is whats known as a BAAS, or Back End As A Service, for creating mobile and web applications.
Originally an independent company founded in 2011, in 2014, Google acquired the platform and it is now their flagship offering for app development. It's primary features include a Realtime noSQL database, and user authentication that all come out of the box and ready to connect to a front end application.
While using Firebase is very convenient in many ways it also limits the scope of a full stack applicaiton by limiting the way we structure our database or backened functionality. It is great for smaller applicaitons that do not need a large back end layer of complexity or to bring in certain features (LIKE AUTHENTICATION)
Authentication is a streamlined, secure way to integrate multiple different Oauth (allowing 3rd parties like Google or GitHub to handle authentication for an app like Reddit or Code Wars). Additionally, Firebase provides analytics and a database functionality out of the box.
Additionally, Firebase provides analytics and a database functionality out of the box that tracks metrics from users location to retention, engagement and many more powerful metrics.
In short - it's a powerful tool to help set up complex backend tasks in our applicaiton.
To setup firebase on our apps we will need to perform the following:
- Signup for a Firebase account
- Set up a React App
- Register an applicaition on Firebase
- Enable Firebase Authentication
- Download our App credientials from Firebase to our
.env
- Install Firebase via NPM
- Set up our React Context to provide auth data to components
- Set up route guards to allow ONLY authenticated users acces
- Handle user logout
Our React app is going to use a service
( just a function ) to create a
getAuth()
instance and call the signInWithPopUp()
method and pass it an instance of a GoogleAuthProvider()
.
This is very similar to how we import and set up our express server.
We then take our app information from our firebaseConfig
that Firebase
gave us when we resitered our app. We will then create a Contex
by calling the useContext()
hook. Don't worry you are not familiar with useContext()
we'll walk through it together (read up on it https://reactjs.org/docs/context.html,)
but for now thinkg of a Context
as a way to share information between components
without them down as props.
Once our users sign in we save the information we get from Google
as state on our Context
as user
. We then set up a useEffect()
hook to watch our user
. When our user
logs out we update state on our Context
and then our useEffect()
hook reroutes our application back to our login
page preventing our router from loading a view unless the user
in our Context
state is valid.
-
Clone this repo!
-
Head to https://firebase.google.com/ and click
Get Started
to sign up for Firebase -
Firebase will biring you to the Project OverView Page. Click the
</>
button to begin a web app.- Give your app a nickname (whatever you want it to be)
- You will be shown a
firebaseConfig
object. Keep it close by - we'll need it
-
In our CRA app - create a
.env
file and paste the following in:
REACT_APP_API_KEY=""
REACT_APP_AUTH_DOMAIN=""
REACT_APP_PROJECT_ID=""
REACT_APP_STORAGE_BUCKET=""
REACT_APP_MESSAGING_SENDER_ID=""
REACT_APP_APP_ID=""
REACT_APP_MEASUREMENT_ID=""
Note we do not need to install dotenv! Create React App allows us to acces .env files but they must start with REACT_APP
in our .env files.
-
Now we need to map all the values from our
firebaseConfig
object to our.env
file. -
Create a
Firebase.js
file. This file will handle all of our logic pertaining to firebase. We will keep it seperate from our components. -
Remmber to install firebase!
npm i fireabase
Te firebase SDK - software development kit - gives us a ton of functionality out of the box. For this lesson we will focus only on implementing Google Oauth. Adding email/passoword login, or other Oauth only requires a few extra steps but is very similar.
More information on Firebase here
More on Oauth here
In order to leverage Firebase authentaction API we need to do the following:
- Import our firebase files and create a
firebaseConfig
object with our.env
variables
import { initializeApp } from 'firebase/app';
import { GoogleAuthProvider, signInWithPopup, getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: process.env.REACT_APP_API_KEY,
authDomain: process.env.REACT_APP_AUTH_DOMAIN,
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
};
- initialize an instance of firebase (much like an express server) with our config
const app = firebase.initializeApp(firebaseConfig);
- create an instance of authentication and set default language
export const auth = getAuth();
auth.useDeviceLanguage();
- create a
provider
for any authentication method we may be using. (facebook, twitter, email/password etc.). For this lesson we will use Google Oauth. *NOTE 'Provider' is a term we will see often in this lesson. In web development, a "provider" typically denotes a component or service responsible for managing and supplying specific functionalities or data to other components within an application.
const googleProvider = new GoogleAuthProvider();
- Export functions that use the
signInWithPopUp()
method we get from firebase, passing in whateverprovider
we created.
export const signInWithGoogle = async () => {
try {
//the signInWithPopUp() method accepts ANY provider we create. This is all our authentication logic
signInWithPopup(googleProvider).then((res) => {
const user = res.user;
console.log(user)
})
} catch (err) {
console.log(err);
}
};
- Don't forget a sign out method!
export const signOut = async () =>{
try {
await auth.signOut()
alert("you've signed out - congrats.")
} catch(err) {
console.log(err)
}
}
Congratulations, you just wrote up a service to leverage Oauth for you react application. Next step is to connect it to our React App.
To understand how a context works in React, let's imagine the following situation:
Visualize this scenario like a game night in Brooklyn with two apartments: one designed like a traditional railroad apartment, and the other as a central open concept.
Each room is having its own game.
- Issue: Passing event info from the entrance (top component) to each room (nested components) becomes unwieldy. I have to move through each room to find the game I'd like to play.
-
Analogy: Imagine a central living space connected to each room with the party host.
-
Benefit: Instead of passing info through each room, participants (components) can directly access the information they need by communicating directly with the host (context).
Conclusion: React context provides a more direct and efficient way for components to access shared information, similar to how an open concept apartment layout allows for shared access without navigating through a series of rooms.
Next, we need to set up a context that will expose a global state value to all of our components. Simply put, if we have a user held in the context's state - the user can navigate our application. If not, we reoute the user to the login page.
Navigate to the providers
folder in our app
$ touch AuthProvider.jsx
This file will be responsible for listening for any changes in our firebase auth
object and then updating the state of our context to be consumed by our components.
First, lets bring in our imports
// firebaseAuthProvider.js
import React, { useEffect, useState, createContext } from "react";
//noice here we are refrenceing the service we set up earlier
import { auth } from "../Services/Firebase";
Next, we need to create a context that our components can consume. Let's initialize it with null
since we will start the app with no user
React provides the powerful createContext()
hook to allow us to create a component that can be accessed from any component in our app by invoking React's useContext()
hook.
export const AuthContext = createContext(null)
Note: When invoked, createContext()
automatically creates a new context object . After creating a context, we have access to a component anywhere in our app called <AuthContext.Provider />
.
Any component nested within this provider has access to a special attribute on <AuthContext.Provider>
called value
.
Other Note: the <AuthContext.Provider value={allOurAuthLogic}>
has one prop - value
.
Keep it that way
While it may be tempting to add additional props as the logic grows it is a best practice to keep all functions, state, and other values exported from a context in one object
value
// any thing nested in our provider can access the value stored within it
<AuthContext.Provider value={user}>
........< all our other components >
</AuthContext.Provider>
Now that we have our Context - AuthContext
we need to perform the following in AuthProvider.js
to managin our context
-
Create
AuthProvider
component that will handleAuthContext
state. -
Render our
AuthContext.Provider
inside of ourAuthProvider
-
Import our
auth
instance fromservices/firebase.js
, listen for changes, and update state accordingly -
Wrap our logic in our very own custom hook
useAtuh()
to acess the context
AuthProvider.js
export const AuthProvider = (props) => {
const [user, setUser] = useState(null);
return ()
}
export const AuthProvider = (props) => {
const [user, setUser] = useState(null);
return (
<AuthContext.Provider value={user}>
{/* props.children will render any nested child components */}
<div>{props.children}</div>
</AuthContext.Provider>
)
}
Note: Wait - what is this { props.children }
you may be asking?
Smply put - the props.children
property is available to us on all components. It's best to think of it as a placeholder for values we don't know yet when desigining components.
In this case, our AuthProvider
component is rendering our AuthContext.Provider
component which we recieved from calling createContext()
.
This AuthContext.Provder
component renders { props.children }
so that React knows that whatever is nested inside AuthContext.Provider
is rendered in our application.
This is refered to as a Higher Order Component
. In the same way a higer order function
accepts or returns a function
a higher order component
accepts or returns components
More on props.children
here - or try it on codepen
Next, lets set up a useEffect()
in AuthProvider
component to listen for changes in the auth
object we brought from firebase.js
by invoking the onAuthStateChanged()
method from our auth
object.
const AuthProvider = (props) => {
const [user, setUser] = useState(null);
useEffect(() => {
//listen for changes
auth.onAuthStateChanged(async (user) => {
console.log(user)
if (user) {
// get return values from Firebase
const { email, displayName, photoURL, uid } = user;
// save them in state
setUser({ email, displayName, photoURL, uid });
} else {
setUser(null);
}
});
}, []);
return (
// render context provider with user state in value
<AuthContext.Provider value={user}>
<div>{props.children}</div>
</AuthContext.Provider>
);
};
Note: the onAuthStateChanged()
method creates something called an Observer
from a library called RXJS
.
This Observer
listens for any changes on the object it was called on will fire whatever callback we specify once the Observer
signals a change. We don't need to get deep under the hood for this lesson but for more on Observables click here
In order to keep with React best practices we should always adhere to the principle of Abstraction
.
Rather than importing complex logic - we can wrap our entire file in a clean and simple hook that anyone can use in our app.
Our hook should express what the purpose of the logic is - in this case Authentication
Let's go with useAuth()
This hook should allow our users to simply access all the logic that we gain by accessing this context.
When a user invokes useAuth
we need to return a call to the useContext()
hook and pass it our authContext
we created
export const useAuth = () => {
return useContext(AuthContext)
}
const AuthProvider = (props) => {
const [user, setUser] = useState(null);
useEffect(() => {
//listen for changes
auth.onAuthStateChanged(async (user) => {
console.log(user)
if (user) {
// get return values from Firebase
const { email, displayName, photoURL, uid } = user;
// save them in state
setUser({ email, displayName, photoURL, uid });
} else {
setUser(null);
}
});
}, []);
return (
// render context provider with user state in value
<AuthContext.Provider value={user}>
<div>{props.children}</div>
</AuthContext.Provider>
);
};
export const useAuth = () => {
return useContext(AuthContext)
}
In order to access our Context - we need to import in in App.js
and nest our other components inside of it
App.js
import { AuthProvider } from "./Providers/AuthProvider";
import { LoggedInPage } from "./Pages/LoggedInPage";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
function App() {
;
return (
<div className="App">
<AuthProvider>
<Router>
<Routes>
<Route exact path="/">
<header className="App-header">LETS LEARN FIREBASE AUTH</header>
<LoginPage />
</Route>
<Route path="/loggedInPage">
<LoggedInPage />
</Route>
</Routes>
</Router>
</AuthProvider>
</div>
);
}
Note: make sure that our Router is nested inside of our <AuthProvider>
failing to do so will throw an error in leveraging useNavigate()
So far we've:
- created a Firebase app to authenticate and record our users
- Leveraged Firebase SDK API to authenticate and save our user in component state
- Created a React Context to allow access to our state value.
- Exported all our logic in a custom hook
useAuth()
Our last step is to connect our signInWithGoogle()
method from firebase.js
to our Login
component and set up a useEffect()
to reroute our user if we are not logged in.
Login.js Handle imports
import React, { useContext, useEffect, useState } from "react";
import { LoggedInPage } from "../Pages/LoggedInPage";
// our custom hook to access our user State
import { useAuth } from "../Providers/AuthProvider";
import { useNavigate } from "react-router-dom";
// our logic to authenticate with firebase
import {
signInWithGoogle,
signOut
} from "../Services/Firebase";
Lets save the state of our AuthContext
and create a useEffect()
to listen for changes and connect our functions to our buttons.
export const Login = () => {
const { user } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (user) {
navigate("/loggedInPage");
}
}, [user, history]);
return (
<div>
<section>
<div>
<div> login works</div>
<button onClick={signInWithGoogle}>Sign in With google</button>
<button onClick={signOut}> sign out</button>
</div>
</section>
</div>
);
};
Now when our user logs in with signInWithGoogle()
method - they will be routed to our LoggedInPage.js
via useNavigate()
.
Lets make sure that LoggedInPage.js
is ready to manage our authenticated user by invoking useEffect()
to make sure we have a user
object. If not - let's reroute our user back to the Login.js
.
Imports && component Initialization
// LoggedInPage.js
import React, { useContext, useEffect, useState } from "react";
import { AuthContext } from "../Providers/AuthProvider";
import { useNavigate } from "react-router-dom";
import { signOut } from "../Services/Firebase";
export const LoggedInPage = () => {
const imgStyle = {
width:'30vh',
height:'30vh'
}
const navigate = useNavigate()
const user = useContext(AuthContext)
useEffect(() => {
if(!user){
navigate("/")
}
}, [user, history]);
const handleLogout = async () => {
signOut();
alert("you've been logged out")
};
return (
<div>
Welcome {user.displayName} !
<div>
<img
style= {imgStyle}
alt="its the users head"
src={user.photoURL}
></img>
<h1>WE KNOW WHAT YOU LOOK LIKE</h1>
</div>
<button onClick={handleLogout}> LOG OUT</button>
</div>
);
}
Now that we have authentcation let's navigate to the firebase console.
From the dashboard, select your app, then on the Project Overview page - select authentication from the sidebar menu.
You should now see a database that firebase uses to keep track of your users. Notice each user has a uniqe uid
. In our apps we can now store whatever information we may be using in our Postgress DB with the uid
key to connect them to our users. Any user that signs in will be recored in the Firebase Authentication table.
Lets review the steps of our authentication
-
We registered an app with firebase and recorded our config variables.
-
Using the firebase SDK ( npm i firebase ) we initilaized an app and passed our config variables
-
We leveraged Firebase's
signInWithPopUp()
function and passed it an instance of Firebase'sgoogleAuthProvider()
-
using React
useContext()
API, we instantiated aAuthContext
and rendered the generated<AuthContext.Provider />
component in a AuthProvider Component that listens for changes on our Firebaseauth
object -
Our
Login.js
page andLoggedIn.js
update theiruser
state whenever the user value changes. -
If we have a user we route the user to certain pages. Otherwise we route them to the login.