React + Firestore : Authentication

react_firebase.png

  1. To get setup please refer to part 1 : React + Firestore : Get Setup In Five Steps.

  2. Please refer to part 2 for the basics of reading and writing to the database in React + Firestore: CRUD.

  3. Please refer to part 3 for how to deploy your application in React + Firestore : Deploying Your Application.


Authentication Setup

auth.png

We’re going to use Google to sign in because you already have a google account and you’ve connected it by being logged into the firebase console.

Other 3rd party logins require setting up an app on the respective social platform and then entering some keys here. This is a bit of a detour so I recommend checking out the documentation if you want to login with Twitter, GitHub or Facebook. If you want to set up email login then you have to think about the mechanics of building a sign-up form and validating it. I recommend The Missing Forms Handbook of React book.

Navigate back to firebase.js file in your react app.

// at the bottom of src/firebase.js
export const auth = firebase.auth()
export const googleAuthProvider = new firebase.auth.GoogleAuthProvider()
// at the top of src/App.js
import { auth, googleAuthProvider } from './firebase'
<button onClick={( ) => auth.signInWithPopup(googleAuthProvider)}>
  Signup/Login
</button>

Use npm start in your terminal to compile the application and then click on the new signup/login button you will see a pop up that lets you sign in using your Google account.

Successfully signing in doesn’t do much though. It just takes you back to the app and everything is the same as it was.

We can use the onAuthStateChanged method on our auth import to detect when a user is authenticated. Let’s listen for changes when the component finished mounting. If a user is authenticated we will add any user data we get to a user property in state.

// in App.js
componentDidMount ( ) {
  ...
  auth.onAuthStateChanged(user => this.setState({ user }))
}

When a user logs in, we store a copy of all of the details that Google gives us about that user in our App component’s state.

{ this.state.user ? (
  <button onClick={( ) => auth.signOut()}>Logout</button>
  ) : (
  <button onClick={( ) => auth.signInWithPopup(googleAuthProvider)}>
    Signup/Login
  </button>
)}

We can replace out Login button with a ternary operator that shows us a logout button if there are any details about a user stored in state.

To log out with Firebase we use the .signOut() method on our auth import.

Now you can login in and out of the app.

Congratulations you have successfully set up authentication with a Google sign in!

Although we just changed a button, the approach opens up a world of possibilities in terms of letting authenticated users do things that other people can’t do unless they sign up.

Only Let People Delete Their Own Data

When a user is authenticated using Google we receive an object full of information about the user. Among the information is something called a unique identifier (or uid). A uid is a unique combination of letters and number that identifies a particular user.

To only let people delete their own data we are going to store their uid alongside their suggestion when it gets created. This lets us check who made the suggestion. Only if the uid is the same an the current user’s uid do we show them a Delete button.

To begin lets modify our submit handler.

handleSubmit = e => {
  e.preventDefault()
  const newSuggestionReference = db.collection('suggestions').doc()
  newSuggestionReference.set({ 
    name: this.textInput.value, 
    id: newSuggestionReference.id 
    createdBy: this.state.user.uid
  })
}

All we did is add a new key called createdBy to the object that gets stored on the database and we gave it a value of auth.uid, which is the unique identifier on the user object we copied into state when a user signed in.

If we go back to the form and add a new suggestion we will see the datum shows up with a uid in the firebase console. 

Only Let People People Vote Once

To practice what we have leanerd we are going to add a vote button beside each suggestion. We are going to configure it so that people can never vote more than once on a suggesstion.

Create a +1 button on each suggestion that we created in the previous post React + Firestore : CRUD.

<button
  onClick={() => {
    db
    .collection('suggestions')
    .doc(topic.id)
    .set({
      votes: {[this.state.user.uid]: true},
      { merge: true })
  }}>
  +1
</button>

The onClick handler finds the doc of the suggestions we clicked on in the suggestions collections and then creates a new property of votes that contains an object with teh current user’s uid and sets it to true.

We then add {merged: true} as a second argument to ensure that a second vote with the same data overwrites the first rather than creating a second entry.

If were not logged in this will throw an error so lets write a condition for when people are not logged in.

onClick={( ) => {
  this.state.user
  ? db
      .collection('students')
      .doc(`${student.id}`)
      .set({
        votes: {[this.state.user.uid]: true},
       { merge: true }
   }) 
   : auth.signInWithPopup(googleAuthProvider)
}}

If a user is not logged in clicking on a vote button will summon the login popup.

Now we can show the number of votes for each suggestion by counting the number of items inside each vote object.

{this.state.user &&student.votes && Object.keys(student.votes).length}

We can use the same idea to conditionally render a -1 button if the someone has already voted on a suggestion.

Instead of checking the length of all of the keys in the votes object we can see if the votes object contains a key that is the same as the current user’s uid.

Object.keys(student.votes).includes(this.state.user.uid)

If this returns true then we render a -1 button, otherwise we render a +1 button.

{ this.state.user && student.votes &&
  Object.keys(student.votes).includes(this.state.user.uid) ? (
    <button>-1</button>
  ) : (
    <button
      onClick={( ) => {
        db
        .collection('suggestions')
        .doc(topic.id)
        .set({
          votes: {[this.state.user.uid]: true}},
          { merge: true })
     }}>
    +1
   </button>
 )}

Now we can add in the logic that ensures the -1 button actually removes a users vote when it is clicked.

To do this we need to use a new method on the firestore module called FieldValue so we need to import the firestore module into the file import firebase from 'firebase'.

Then we can create the -1 onClick handler.

onClick={( ) => {
  db
  .collection('suggestions')
  .doc(topic.id)
  .set({
    votes: {[this.state.user.uid]: firebase.firestore.FieldValue.delete()}},
    { merge: true }
  )}
}

Congratulations, you’ve done it!

We now have a voting application where you can vote for the next Firebase feature you would like to learn about or you could vote on other people’s suggestions. We know each vote is authentic because only logged in users could vote and we made sure they could only vote once.

 
27
Kudos
 
27
Kudos

Now read this

Typechecking Non-React Code

When you build a component in React you have the option to explicitly declare the type of props that it can, or must, accept by using the prop-types library. This may seem like pointless extra work when you’re starting out, but when you... Continue →