Skip to content

MichaelRMC/firebase-react

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Firebase Auth with Google and React Context API

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.

What is authentication?

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.

What is Firebase?

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)

What's So Special About it?

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.

How will this work?

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

What we're doing in React

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.

Project Set Up

  • 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

Firbebase API

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 whatever provider 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.

React useContext()

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.

Railroad Apartment (Top-Down Approach)

  • 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.

Central Open Concept Apartment (Context Approach)

  • 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.

Set-up

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>

Managing AuthContext

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 handle AuthContext state.

  • Render our AuthContext.Provider inside of our AuthProvider

  • Import our auth instance from services/firebase.js, listen for changes, and update state accordingly

  • Wrap our logic in our very own custom hook useAtuh() to acess the context

Create UserProvdier component

AuthProvider.js

export const AuthProvider = (props) => {
  const [user, setUser] = useState(null);
   return ()
  }
 
  

Render our new AuthContext.Provider component

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

Auth and State

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

Our Custom Hook

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()

Putting It All Together

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.

Wrapping it all up

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's googleAuthProvider()

  • using React useContext() API, we instantiated a AuthContext and rendered the generated <AuthContext.Provider /> component in a AuthProvider Component that listens for changes on our Firebase auth object

  • Our Login.js page and LoggedIn.js update their user 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.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 81.6%
  • CSS 13.4%
  • HTML 5.0%