DEV Community

Cover image for Authenticate users with firebase and react.
Tallan Groberg for ITNEXT

Posted on • Updated on

Authenticate users with firebase and react.

In this article, we are going to make a basic user auth with firebase. If you have experience with any other type of user auth, you probably got frustrated.

Firebase does have a learning curve but I have found it small compared to other alternatives.

Firebase is going to do a lot of the heavy backend functionality

If you would like to see what this app does here is the "finished" product you can here

Why is this tutorial useful?

This is how to leverage firebase so that you don't have to create your own backend, encrypt your user's passwords or go through the hassle of deploying a backend application.

Prerequisites:

  1. understanding of JavaScript including how to pass arguments to functions and asynchronous code.

  2. understanding of react, context, hooks with create-react-app.

  3. Text editor of your choice.(I will use vscode)

  4. A Firebase account

  5. basic understanding of the command line.

  6. knowledge of git.

Optional: bash command line/Mac OS. You can do this without it but I’ll be using it for this tutorial.

first, make a new firebase project by visiting https://firebase.com.

Alt Text

Click on a new project.

Alt Text

click "my first project" and then you can name your project whatever you want.

Click continue.

Alt Text

You can choose not to have google analytics and it should not interfere with this tutorial, I left it on, so you will see parts of my code where it’s enabled.

Click continue.

Alt Text

You will be prompted to select an account.

Alt Text

select the default account, then click create project.

Alt Text

you should now see this.

Alt Text

you should be in your firebase console for this project.

Alt Text

click on authentication on the left side navigation.

Alt Text

click set up sign-in method.

Alt Text

here is a wide variety of ways to set up users signing into our apps. We are going to do the easiest way for this tutorial.

click email and password.

Alt Text

Click enable.

Alt Text

Save.

Make sure that it actually got enabled.

Alt Text

Now go to the project overview.

Alt Text

We need to get info about how our app can send and receive firebase data, so we have to get API keys and other sensitive information given to us in the form of an SDK.

Click on the brackets to begin.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zzpeg5dqj7qmlewy87h9..

We will be creating a react app and adding everything inside the script tag to the react project.

Alt Text

since we don't have a firebaseIndex.js we can't add it yet.

this is everything we have to do on the firebase console for our project.

make a new react app.

create-react-app firebaseauthtutorial 
Enter fullscreen mode Exit fullscreen mode

cd the app

cd firebaseauthtutorial
Enter fullscreen mode Exit fullscreen mode

this is a good moment to plan out what kind of packages are wanted. these will all be installed via npm.

  1. firebase. if this was an ordinary javascript, we would use the whole script take and the SKD.

  2. react-router-dom.

this is so that when a user logs in we display components only accessible by users.

  1. dotenv, the best habit you can have with making apps that contain user data or leveraging APIs' (like this app will) is to ensure that hackers can't get access to your API keys, encryption techniques or other users sensitive info.

dotenv allows you to save sensitive information as environment wide variables, in a way that you can't publish to a remote repo but still be able to use in your app.

run an npm install on the command line for all the packages

pro tip: make sure that you are in the root directory of the project before you run npm install

npm install firebase dotenv react-router-dom
Enter fullscreen mode Exit fullscreen mode

now open the project.

I'm using vscode so this is how from the command line.

code .
Enter fullscreen mode Exit fullscreen mode

look at the package.json file and you should see the packages that you installed.

Alt Text
package.json

moving SDK firebase in the app.

before you copy and paste the SDK into our file, its best practice to add the .env file to the .gitignore so that you don't publish your environment variables to github. It is very easy to forget.

then add the API keys to the .env

then reference them from the firebaseIndex.js we are about to create to the .env file.

this way, you are never in danger of publishing your keys while following this tutorial.

Click on your .gitignore

Alt Text

write .env anywhere in the file

Alt Text

then right-click a blank spot in the root directory. (if you don't have one you can minimize the outline to reveal space.)

Alt Text

Alt Text

copy and paste the following variables to the .env file

REACT_APP_API_KEY=

REACT_APP_AUTHDOMAIN=

REACT_APP_BASEURL=

REACT_APP_PROJECT_ID=

REACT_APP_STORAGEBUCKET=

REACT_APP_MESSAGING_SENDER_ID=

REACT_APP_APP_ID=

REACT_APP_MEASUREMENT_ID=
Enter fullscreen mode Exit fullscreen mode

Including the quotations copy and paste the info from the SDK one by one. API key, auth domain, baseurl ect...

you should have something like this.

your info from firebase.

REACT_APP_API_KEY="your secret api key"
REACT_APP_AUTHDOMAIN="your secret authdomain"
REACT_APP_BASEURL="your secret baseurl"
REACT_APP_PROJECT_ID="your secret projectid"
REACT_APP_STORAGEBUCKET="your secret storeagebucket"
REACT_APP_MESSAGING_SENDER_ID="your secret messaging sender id"
REACT_APP_APP_ID="your secret app id"
REACT_APP_MEASUREMENT_ID="your secret measurment id"

Enter fullscreen mode Exit fullscreen mode

now the easy part.

Begin by making the folder to keep firebases SDK and the helper methods for the auth.

try and do this from your text editor.

by right-clicking the src folder and click new folder.

Alt Text

name the folder firebase.

Alt Text

now right-click the firebase folder and add a firebaseIndex.js

Alt Text

firebaseIndex.js.

Alt Text

import firebase at the top of the firebaseIndex.js file along with the features you want from it.

import firebase from 'firebase'
import 'firebase/auth'
import 'firebase/app'
Enter fullscreen mode Exit fullscreen mode

now that your environment variables are already set up app-wide you can copy and paste this SDK to reference your sensitive data inside the firebaseIndex file with the code I provide.

var firebaseConfig = {
  apiKey: process.env.REACT_APP_API_KEY,
  authDomain: process.env.REACT_APP_AUTHDOMAIN,
  databaseURL: process.env.REACT_APP_BASEURL,
  projectId: process.env.REACT_APP_PROJECT_ID,
  storageBucket: process.env.REACT_APP_STORAGEBUCKET,
  messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_APP_ID,
  measurementId: process.env.REACT_APP_MEASUREMENT_ID
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.analytics();

Enter fullscreen mode Exit fullscreen mode

add firebase.auth() helper method underneath the analytics() method.


firebase.auth()
Enter fullscreen mode Exit fullscreen mode

we are going to need the firebaseConfig object in another file so it needs to be exported

export default {
  firebaseConfig, 
}
Enter fullscreen mode Exit fullscreen mode

the whole file should look like this.



import firebase from 'firebase'
import 'firebase/auth'
import 'firebase/app'

var firebaseConfig = {
  apiKey: process.env.REACT_APP_API_KEY,
  authDomain: process.env.REACT_APP_AUTHDOMAIN,
  databaseURL: process.env.REACT_APP_BASEURL,
  projectId: process.env.REACT_APP_PROJECT_ID,
  storageBucket: process.env.REACT_APP_STORAGEBUCKET,
  messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_APP_ID,
  measurementId: process.env.REACT_APP_MEASUREMENT_ID
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.analytics();
firebase.auth()

export default {
  firebaseConfig, 
}

Enter fullscreen mode Exit fullscreen mode

if you followed these steps you could have pushed to github at any time and it would not have saved your keys.

Adding the auth methods.

inside your firebase folder make a file called auth methods, this is where to keep an object that contains the signin, signup, signout, functions.

Alt Text

Alt Text

at the top import two things, firebaseConfig object and firebase from firebase like so.

import firebaseconfig from './firebaseIndex'
import firebase from 'firebase'
Enter fullscreen mode Exit fullscreen mode

now make an export and make an auth methods object.

export const authMethods = {

// firebase helper methods go here... 

}
Enter fullscreen mode Exit fullscreen mode

we are going to send this to context where this will be the top of a chain of methods that link all the way to the form for signin.

Alt Text

these are going to be key/value pairs that we give anonymous functions for signing in.


 export const authMethods = {
  // firebase helper methods go here... 
  signup: (email, password) => {

    },
  signin: (email, password) => {

    },
  signout: (email, password) => {

    },
  }
Enter fullscreen mode Exit fullscreen mode

this looked really unusual the first time I saw it. This will make a lot more sense after we start calling on it from context.

this is from the firebase documentation for user auth.

signup: (email, password) => {
    firebase.auth().createUserWithEmailAndPassword(email,password) 
      .then(res => {
        console.log(res)
      })
      .catch(err => {
        console.error(err)
      })
    },
Enter fullscreen mode Exit fullscreen mode

I want to test if this code works before I start adding the other methods.

to do that build the context and signup form and see if firebase will respond.

Creating context for our application.

right-click on the src folder and make a new folder called provider.

Alt Text

Alt Text

right-click on provider and make a file called AuthProvider.js

Alt Text

Alt Text

make a functional component, add props.

import React from 'react';

const AuthProvider = (props) => {
  return (
    <div>

    </div>
  );
};

export default AuthProvider;

Enter fullscreen mode Exit fullscreen mode

outside of the function, make a firebaseAuth variable and make it equal to react context.

export const firebaseAuth = React.createContext()
Enter fullscreen mode Exit fullscreen mode

we have to export it so that we can access the useContext hook.

erase the div tags and make the provider inside of the return for the AuthProvider I'm not going to explain everything that is happening here but if you want to know more about context this is an article where I explain context and the useContext hook.

const AuthProvider = (props) => {
  return (
    <firebaseAuth.Provider
    value={{
      test: "context is working"
    }}>
      {props.children}

    </firebaseAuth.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

AuthProvider.js

now we need to wrap our App.js in the AuthProvider component in the index.js file.

we also need to import our ability to route components dynamically, since we are already in this file, add BrowserRouter from react-router-dom.

start by importing the AuthProvider and BrowserRouter at the top.

import AuthProvider from './provider/AuthProvider'
import {BrowserRouter} from 'react-router-dom'
Enter fullscreen mode Exit fullscreen mode

then make an App sandwich with BrowserRouter and AuthProvider.

ReactDOM.render(
<BrowserRouter>
  <AuthProvider>
    <App />
  </AuthProvider>
</BrowserRouter>
, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

two things,

go to the App.js, at the top change how react is imported to include useContext and React.

import {firebaseAuth} so that we can destructure the test key/value pair out of it like this.

import React, {useContext} from 'react';
import {firebaseAuth} from './provider/AuthProvider'
Enter fullscreen mode Exit fullscreen mode

inside the function destructure test from the firebaseAuth variable.

console.log test.

   const {test} = useContext(firebaseAuth)
    console.log(test)

Enter fullscreen mode Exit fullscreen mode

go back to the terminal and start the server.

npm start
Enter fullscreen mode Exit fullscreen mode

inspect with the dev tools and you should see this.

Alt Text

connecting to authMethods

now that we have context App wide, go back to the AuthProvider.js and import the authMethods.

import {authMethods} from '../firebase/authmethods'
Enter fullscreen mode Exit fullscreen mode

This file to be the middle man between firebase and the Signup component we are about to make,

that means all the stateful logic will be housed here.

make a function called handleSignup inside the AuthProvider.

const handleSignup = () => {
    // middle man between firebase and signup 

  }
Enter fullscreen mode Exit fullscreen mode

Pass it as a value in the firebaseAuth.Provider

 <firebaseAuth.Provider
    value={{
      //replaced test with handleSignup
      handleSignup
    }}>
      {props.children}

    </firebaseAuth.Provider>

Enter fullscreen mode Exit fullscreen mode

now change test with handleSignup in the App.js

 const {handleSignup} = useContext(firebaseAuth)
    console.log(handleSignup)

Enter fullscreen mode Exit fullscreen mode

App.js

you should see

Alt Text

in the AuthProvider, add the authMethod.signup() to the handleSignup.

  const handleSignup = () => {
    // middle man between firebase and signup 
    console.log('handleSignup')
    // calling signup from firebase server
    return authMethods.signup()
  }
Enter fullscreen mode Exit fullscreen mode

make a components folder and Signup.js component, recreate the same functionality where we want it to end up so that we can define our routing in the App.js

Alt Text

Alt Text

make the Signup.js

Alt Text

Alt Text

make a basic component


// add useContext
import React, {useContext} from 'react';

const Signup = () => {


  return (
    <div>
      Signup
    </div>
  );
};

export default Signup;
Enter fullscreen mode Exit fullscreen mode

destructure the handleSignup function out of context just like in the App.js

const {handleSignup} = useContext(firebaseAuth)
    console.log(handleSignup)
Enter fullscreen mode Exit fullscreen mode

__

in the App.js add the beginnings of react-router-dom by removing the boilerplate and adding Switch and Route, setting the signup to be rendered by the Route.


import {Route, Switch} from 'react-router-dom'
import Signup from './component/Signup'
Enter fullscreen mode Exit fullscreen mode

App.js

 return (
    <>
    {/* switch allows switching which components render.  */}
      <Switch>
        {/* route allows you to render by url path */}
        <Route exact path='/' component={Signup} />

      </Switch>
    </>
  );
Enter fullscreen mode Exit fullscreen mode

if everything worked you should see a white screen with signup.

Alt Text

make a signup form.

return (
    <form>
      {/* replace the div tags with a form tag */}
      Signup
      {/* make inputs  */}
      <inputs  />
      <button>signup</button>
    </form>
  );
Enter fullscreen mode Exit fullscreen mode

at this point, it might be tempting to make state here. but we want the context to be the single source of truth so that if a user toggles between login and signup, whatever they typed in will persist.

go back to the AuthProvider and start setting up state.

we need a piece of state for a token from firebase and for user data.

import useState next to React.

import React, {useState} from 'react';
Enter fullscreen mode Exit fullscreen mode

AuthProvider.js

the pieces of state that we want will be.

  1. token as null (then a string once we get a token from firebase), more about json web tokens.

  2. input as an object with email and password both strings.

  3. errors as an array, so that error messages can be displayed to the users.

add those states to the AuthProvider.js


const [inputs, setInputs] = useState({email: '', password: ''})
  const [errors, setErrors] = useState([])
  const [token, setToken] = useState(null)

Enter fullscreen mode Exit fullscreen mode

add inputs to the value object of the provider.

<firebaseAuth.Provider
    value={{
      //replaced test with handleSignup
      handleSignup,
      inputs,
      setInputs,

    }}>

Enter fullscreen mode Exit fullscreen mode

in the Signup.js get them from the authContext with the useContext hook like this.

  const {handleSignup, inputs, setInputs} = useContext(firebaseAuth)
Enter fullscreen mode Exit fullscreen mode

make handleChange and handleSubmit functions as basic forms.

const handleSubmit = (e) => {
    e.preventDefault()
    console.log('handleSubmit')

  }
  const handleChange = e => {
    const {name, value} = e.target
    console.log(inputs)
    setInputs(prev => ({...prev, [name]: value}))
  }
Enter fullscreen mode Exit fullscreen mode

change the form and input fields to work with the form functions.

<form onSubmit={handleSubmit}>
      {/* replace the div tags with a form tag */}
      Signup
      {/* make inputs  */}
      <input onChange={handleChange} name="email" placeholder='email' value={inputs.email} />
      <input onChange={handleChange} name="password" placeholder='password' value={inputs.password} />
      <button>signup</button>
    </form>

Enter fullscreen mode Exit fullscreen mode

if you did everything correctly and ran a test that looks like this...

Alt Text

here is the error message you would have gotten.

Alt Text

the reason we got this error is that we didn't pass the authMethods.signup the email and password arguments that it was expecting.

pass inputs.email and inputs.password into authMethods.signin

authMethods.signup(inputs.email, inputs.password)
Enter fullscreen mode Exit fullscreen mode

when you do a test like this.

Alt Text

you should get a response like this.

Alt Text

but if you try and do it twice you will get an error.

Alt Text

this is because you can't do this twice. all of the emails have to be unique.

to make it so that the error message displays to the user we have to do the following.

  1. in the AuthProvider.js, pass setErrors as an argument along with email and password,

this is the only way I could figure out how to do this. whenever you do have to pass more than one argument to a function you should have a good justification.

  1. in the authMethods.js on the signup(), add the third argument at the top and in the .catch, we will have the error messages save to state in the errors array.

  2. have the error display to the screen by passing it to the Signup.js and mapping through the array.

1.

//sending setErrors
  authMethods.signup(inputs.email, inputs.password, setErrors)
console.log(errors)
Enter fullscreen mode Exit fullscreen mode

now add the setErrors message along with email and password.

AuthProvider.js
2.

  //catching setErrors
 signup: (email, password, setErrors) => {
Enter fullscreen mode Exit fullscreen mode

authMethods.js

change the catch to the setErrors include prev in case it's more than one error

.catch(err => {
       //saving error messages here
        setErrors(prev => ([...prev, err.message]))
      })

Enter fullscreen mode Exit fullscreen mode

if it worked and you console logged it, you should see this error.

Alt Text

  1. add errors to the value object of the Provider
 <firebaseAuth.Provider
    value={{
      //replaced test with handleSignup
      handleSignup,
      inputs,
      setInputs,
//added errors to send to Signup.js
      errors,
    }}>
      {props.children}
    </firebaseAuth.Provider>

Enter fullscreen mode Exit fullscreen mode

AuthProvider.js

destructure it from useContext from the Signup.js

const {handleSignup, inputs, setInputs, errors} = useContext(firebaseAuth)

Enter fullscreen mode Exit fullscreen mode

Signup.js

now add a ternary that will only show up if an error occurs.

  <button>signup</button>
      {errors.length > 0 ? errors.map(error => <p style={{color: 'red'}}>{error}</p> ) : null}
    </form>
Enter fullscreen mode Exit fullscreen mode

if everything worked you will get your error on the screen.

Alt Text

if you want to filter duplicates you can find out or see how I did on the repo but this tutorial is getting long and a couple more things to do.

to make it so that you can enable multiple emails per account.

go to firebase inside of this project, click on authentication.

click on signin method

scroll to the bottom and where it says advanced in small black letters. it says one account per email in bold.

Click the blue change button

Alt Text

click allow multiple accounts with the same email.

this will help us move faster with testing but don't forget to switch it back later.

  1. The same way that we set an error we are going to save the token to localStorage and the token's state in the AuthProvider.

  2. make it so that we can only see some components if we have a token.

  3. redirect to that page if the token in local storage matches the token in state.

  4. repeat the process for signin.

  5. erase the token and pushing the user out of the authenticated parts of our app with the login method.

  6. go to the AuthProvider.js and add setToken as another argument after setErrors.

//sending setToken function to authMethods.js
 authMethods.signup(inputs.email, inputs.password, setErrors, setToken)
    console.log(errors, token)
Enter fullscreen mode Exit fullscreen mode

AuthProvider.js

add this as a 4th argument at the top.

// added the 4th argument
 signup: (email, password, setErrors, setToken) => {
Enter fullscreen mode Exit fullscreen mode

inside the .then, underneath the console.log(res)...

I am about to save you so much time you would have to spend digging through the res object to find the token.

this is also about to be a little messy with the async code.

signup: (email, password, setErrors, setToken) => {
    firebase.auth().createUserWithEmailAndPassword(email,password) 
      //make res asynchronous so that we can make grab the token before saving it.
      .then( async res => {
        const token = await Object.entries(res.user)[5][1].b
          //set token to localStorage 
          await localStorage.setItem('token', token)
          //grab token from local storage and set to state. 
          setToken(window.localStorage.token)
        console.log(res)
      })
      .catch(err => {
        setErrors(prev => ([...prev, err.message]))
      })
    },
Enter fullscreen mode Exit fullscreen mode

authMethods.js

now if you make yet another account and go to the browsers dev tools

Alt Text

Alt Text

Alt Text

_2. signing in _

we are going to copy and paste a lot of what we have for signup and easily configure it for login.

we will start from the bottom of the component tree by making a Signin component slightly change file by file until it works in the authMethods.

start by making a new file called Signin.js

Alt Text

copy and paste everything from the Signup.js to the Signin.js

highlight everywhere it says signup and change that to signin

Click on the name of the react component and Command + d if you are using a mac. Otherwise, you can use ctrl + f and type it in at the top.

I only had 3 words by remember to change handleSignup to handleSignin using the same method.

change the button as well.

Now go to the App.js and import the file.

import Signin from './component/Signin'
Enter fullscreen mode Exit fullscreen mode

make sure that component folder on the import is singular.

add a new route for the Signin

<Route exact path='/' component={Signup} />
        <Route exact path='/signin' component={Signin} />

Enter fullscreen mode Exit fullscreen mode

your signin component will render now if you type in the http://localhost:3000/signin but as soon as you do click the button it will crash because there is no handleSignin function.

to fix that we can go to the AuthProvider.js and copy and paste changing the wording just like we did for signup. then add the handleSignin function to the value object.

const handleSignin = () => {
    //changed to handleSingin
    console.log('handleSignin!!!!')
    // made signup signin
    authMethods.signin(inputs.email, inputs.password, setErrors, setToken)
    console.log(errors, token)
  }

Enter fullscreen mode Exit fullscreen mode

now to add that function to the firebaseAuth.Provider

 <firebaseAuth.Provider
    value={{
      //replaced test with handleSignup
      handleSignup,
      handleSignin,
      inputs,
      setInputs,
      errors,
    }}>
      {props.children}
    </firebaseAuth.Provider>
Enter fullscreen mode Exit fullscreen mode

AuthProvider.js

now go to authMethods.js and do something similar, instead of createUserWithEmailAndPassword, change to... signInWithEmailAndPassword()

signin: (email, password, setErrors, setToken) => {
    //change from create users to...
    firebase.auth().signInWithEmailAndPassword(email,password) 
      //everything is almost exactly the same as the function above
      .then( async res => {
        const token = await Object.entries(res.user)[5][1].b
          //set token to localStorage 
          await localStorage.setItem('token', token)
          setToken(window.localStorage.token)
            console.log(res)
      })
      .catch(err => {
        setErrors(prev => ([...prev, err.message]))
      })
    },

Enter fullscreen mode Exit fullscreen mode

Alt Text

if you didn't delete your token from local storage then a token will still be there.

Alt Text

almost there!!

  1. make a home component and only allow users with tokens to get there.

  2. make a signout button that deletes the token and pushes the user away from the page with react-router-dom.

since you should already be in the authMethods.js we will start from the top and go to the bottom this time.

this method is really simple compared to the other two because we aren't using firebase to keep user's status there.

//no need for email and password
signout: (setErrors, setToken) => {
      // signOut is a no argument function
    firebase.auth().signOut().then( res => {
      //remove the token
      localStorage.removeItem('token')
        //set the token back to original state
        setToken(null)
    })
    .catch(err => {
      //there shouldn't every be an error from firebase but just in case
      setErrors(prev => ([...prev, err.message]))
      //whether firebase does the trick or not i want my user to do there thing.
        localStorage.removeItem('token')
          setToken(null)
            console.error(err.message)
    })
    },
  }
Enter fullscreen mode Exit fullscreen mode

go to AuthProvider.js and make a signout function

const handleSignout = () => {
    authMethods.signout()
  }

Enter fullscreen mode Exit fullscreen mode

add the method to the Provider

setInputs,
errors,
handleSignout,
Enter fullscreen mode Exit fullscreen mode

now we need a component for this to be useful which we haven't done yet.

make a Home.js, and a basic React component inside it.

Alt Text

Alt Text

import React from 'react';

const Home = (props) => {
  return (
    <div>
      Home
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

import useContext and firebaseAuth

import React, {useContext} from 'react';
import {firebaseAuth} from '../provider/AuthProvider'
Enter fullscreen mode Exit fullscreen mode

between return and Home inside the Component, destructure signout from useContext

  const {signout,} = useContext(firebaseAuth)
Enter fullscreen mode Exit fullscreen mode

in the return statement. add login successful, then a button to call on signout.

 return (
    <div>
      Home, login successful!!!!!!
      <button onClick={signout}>sign out </button>
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

before we can test it, we need to go back up our component tree and change how strict it is to access each component.

in the App.js we are going to use a ternary statement to make it so that users can't get to the home component without a token saved to state.

import the Home component in the App.js.


import Home from './component/Home'
Enter fullscreen mode Exit fullscreen mode

destructure the token out of firebaseAuth with useContext

  const { token } = useContext(firebaseAuth)
  console.log(token)
Enter fullscreen mode Exit fullscreen mode

when you use Route to render the Home component, add a ternary statement checking the data type of the token

this means that setting up the "/" or root URL differently.

change your Home components route to use the render prop instead of the component prop. and designate the URL paths more strictly.

        <Route exact path='/' render={rProps => token === null ? <Signin /> : <Home />} />
        <Route exact path='/signin' component={Signin} />
        <Route exact path='/signup' component={Signup} />
Enter fullscreen mode Exit fullscreen mode

in the AuthProvider.js, add the token to the value object.

<firebaseAuth.Provider
    value={{
      //replaced test with handleSignup
      handleSignup,
      handleSignin,
      token,
      inputs,
      setInputs,
      errors,
      handleSignout,
    }}>
      {props.children}
    </firebaseAuth.Provider>
Enter fullscreen mode Exit fullscreen mode

now users can signin and signout. One final touch, make it so that when a user signs up, react-router-dom will send them to the home page.

go to the Signup.js and import withRouter from react-router-dom

import {withRouter} from 'react-router-dom'
Enter fullscreen mode Exit fullscreen mode

pass the default export to the withRouter higher-order component


export default withRouter(Signup);
Enter fullscreen mode Exit fullscreen mode

add props to the Signup component

const Signup = (props) => {
Enter fullscreen mode Exit fullscreen mode

now we have access to prop.history.push("/goAnyWhereInApp")

now make handleSubmit an async function and await the handleSignup and then push to the root URL.

const handleSubmit = async (e) => {
    e.preventDefault()
    console.log('handleSubmit')
    //wait to signup 
    await handleSignup()
    //push home
    props.history.push('/')
  }
Enter fullscreen mode Exit fullscreen mode

you might have a delay, but once you get your credentials it will work.

if you want to publish this sight here is how with surge. I am a big fan and am doing these firebase tutorials because of a dev who has suffered much at the hands of heroku

this is the finished product

this is the github give it a star if you can.

Finally, that is it

you now have a static site with powerful backend functionality.

I will be doing a lot more tutorials on firebase.

please like and share if you found this tutorial beneficial.

the firebase docs are helpful but I have a few things in here that make it so much easier to transpose to a react project.

if you have anything to say please add it to the comments below.

Top comments (22)

Collapse
 
alexsatchell profile image
Alexander Satchell

Hey Tallan, thank you for helping me get my User Auth set up! I have a quick question, Whenever I refresh the page, my token leaves local storage and the user is signed out. Is there a function I can employ in the authMethod.js file to prevent this from happening? Thanks!

Collapse
 
tallangroberg profile image
Tallan Groberg

I can’t help you out for a couple weeks, I’m very sorry, I’m currently on vacation.

I will do some research as soon as I can and I really appreciate you taking the time to read my tutorial and asking questions but I cannot answer them in a timely manner.

Collapse
 
arnelamo profile image
Arne Pedersen

Hey, Tallan. Thanks for a nice tutorial.
When I came to the part where you store the token in localStorage, I couldn't find the token at the same place in the res Object. I then started Googling this and it seems firebase automatically stores the user's credentials in local storage, and reloads it from there when the app restarts/page reloads, under Storage -> IndexedDB.

If the response object has changed since you created this tutorial, what would you suggest I do to move forward?

Collapse
 
tallangroberg profile image
Tallan Groberg

I’m going to have to look at the res object from firebase and all of application storage in order to give you the best answer, but I did notice that the token was in the indexDB when I wrote this tutorial. I found it easier to dig through object from firebase and save it to local storage rather than try and access indexDB. My recommendation is the dig through the object and save the token to local storage, however it is worth assessing the difficulty of cutting out the middle man and just using indexDB.

I will give you a better answer than this when I have the time to look at the code again.

Collapse
 
arnelamo profile image
Arne Pedersen • Edited

Wow, that's fantastic. I'm looking forward to hear what you find.

I can see that some people recommend useEffect and something called onAuthStateChanged, like so:

useEffect(() => {
   const unsub = firebase.auth().onAuthStateChanged((user) => {
      user ? setUser(user) : setUser(null);
    });
    return () => {
      unsub();
    };
  });
  return user;
};

I guess this is using the stored data in the indexedDB(?), but if you would consider this as a viable option - how would you recommend integrating this with the context? If you could comment on this as well, it would be really awesome!

Collapse
 
somtookaforr profile image
Somtochukwu Okafor • Edited

Thanks Tallan for the amazing tutorial. I ran into some errors trying to reproduce it in my react project.
"TypeError: Cannot destructure property 'token' of 'Object(...)(...)' as it is undefined."

Please could you look at this?

Collapse
 
alexomosa profile image
Alex-omosa

try checking the way you are importing firebaseAuth ,
Import like this
import { firebaseAuth } from './../provider/AuthProvider';
instead of like this
import firebaseAuth from './../provider/AuthProvider';

I got the same error, this how I solved it

Collapse
 
berabulut profile image
Hüseyin Bera Bulut

Hi Tallan thanks for tutorial, it's brilliant. There are two errors in the code above.
You fixed them in your source repo but if someone is stuck, it may help.

const {signout,} = useContext(firebaseAuth)

-> signout is need to be replaced by handleSignout.


const handleSignout = () => {
authMethods.signout()
}

-> we need to pass setErrors and setToken as parameters to authMethods.signout().

Collapse
 
jcollyer profile image
jeremy collyer

can you use:
const token = await res.user.refreshToken
instead of:
const token = await Object.entries(res.user)[5][1].b
?

Collapse
 
silenttush profile image
Tushar Kushwaha

for those having issues of session lost while refreshing.

add this in AuthProvider.js

const [token,setToken] = useState(localStorage.getItem("token"));

Collapse
 
devsunshine profile image
ajhopen

Great fix. My build was stuck that it has already logged in. This corrected it.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
alexomosa profile image
Alex-omosa • Edited

Try this
Just enable FCM in your google cloud console,

More Stackoverflow: stackoverflow.com/questions/600060...

Collapse
 
tallangroberg profile image
Tallan Groberg

Yes that would be really cool. How do I do that?

Collapse
 
lowry profile image
Will Lowry • Edited

I get the following, anyone else come up with this problem?

TypeError: Cannot destructure property 'handleSignin' of 'Object(...)(...)' as it is undefined.

const SignIn = () => {

const {handleSignin, inputs, setInputs, errors} = useContext(firebaseAuth)

const handleSubmit = (e) => {
e.preventDefault()

Collapse
 
danishalikhan688 profile image
danishalikhan688

thank you so much for such a nice tutorial its working

Collapse
 
miketaheny profile image
miketaheny

Tallan, thanks for restoring my sanity as I was struggling with this for a solid day before your tutorial provided some clarity! Much appreciated.

Collapse
 
tallangroberg profile image
Tallan Groberg

I’m glad you found it helpful, auth can be really confusing and I think this is the simplest way to do it.

What was the hardest part?

Collapse
 
karl74 profile image
Carlos Castro

Tallan, Thank so much for writing this tutorial. I wouldn't be able to continue my project without the information you provided.

Collapse
 
tallangroberg profile image
Tallan Groberg

You’re welcome, I love what I do and I love to share it.

Collapse
 
akshaymittal143 profile image
Akshay Mittal

great post, you might have to update little bit because firebase v9 is little different.

Collapse
 
mkaschke profile image
Martin Kaschke

I would like to recommend to use "ReactFire". Works really well. github.com/FirebaseExtended/reactfire