React + Firestore : Authentication
To get setup please refer to part 1 : React + Firestore : Get Setup In Five Steps.
Please refer to part 2 for the basics of reading and writing to the database in React + Firestore: CRUD.
Please refer to part 3 for how to deploy your application in React + Firestore : Deploying Your Application.
Authentication Setup
- To set up authentication in the firebase console go to the authentication section under DEVELOP in the left-hand pane (1), clicking on the ‘sign-in method’ tab (2), find the provider you want (3) and enabling it (4), before saving your settings (5).
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.
- Create and export a constant that is responsible for all things firebase authentication. We must also create Google authentication provider so that we can use it when we want to log in using a Google account.
// at the bottom of src/firebase.js
export const auth = firebase.auth()
export const googleAuthProvider = new firebase.auth.GoogleAuthProvider()
- Import
auth
andgoogleAuthProvider
into tehApp.js
component.
// at the top of src/App.js
import { auth, googleAuthProvider } from './firebase'
- Create a button that we can use to sign in. Add on onClick handler that implements the
.signInWithPopup()
method on theauth
constant we just imported. Finally, let’s pass thegoogleAuthProvider
to this method.
<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.
- Listen for authentication changes in our App
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.
- Conditionally show a logout button if a user is signed in.
{ 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.