15
Authentication with React and Firebase
Authentication is something that most public applications will need, and it is a pain to configure, so I figured documenting how I do authentication with React and Firebase will be helpful for others (and for myself when I inevitably forget a step). I will update this blog as my process for authentication changes. Also, my initial strategy is derived from h3webdevtut's tutorial.
This tutorial assumes you already have created a Firebase project, a web app for the project, and you have a React project set up.
For a quick copy/paste, scroll to the bottom of this page.
npm install firebase
Create a file called /src/fire.js
.
// firebaseConfig at /src/fire.js
import firebase from 'firebase'
const firebaseConfig = {
apiKey: 'yourAPIKey',
authDomain: 'yourAuthDomain',
databaseURL: 'yourDatabaseURL',
projectId: 'yourProjectId',
storageBucket: 'yourStorageBucket',
messagingSenderId: 'yourMessagingSenderId',
appId: 'yourAppId'
}
const fire = firebase.initializeApp(firebaseConfig);
export default fire;
Optional (will update later): You can put your firebaseConfig variables in a .env
file.
In the Firebase console, enable the email and password sign-in method.
In /src/App.js
(or whatever file your app is in), import useState
and useEffect
:
import React, { useState, useEffect } from 'react'
import fire from './fire'
Add states for keeping track of the user:
const App () => {
const [user, setUser] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');
const [hasAccount, setHasAccount] = useState(false);
...
}
Define two helper functions to clear inputs and errors:
clearInputs()
:
const clearInputs = () => {
setEmail('');
setPassword('');
}
clearErrors()
:
const clearErrors = () => {
setEmailError('');
setPasswordError('');
}
handleLogin()
for when a user logs in:
const handleLogin = () => {
clearErrors();
fire
.auth()
.signInWithEmailAndPassword(email, password)
.catch(err => {
// user firebase err code to determine kind of error
// and handle it accordingly
switch(err.code) {
case "auth/invalid-email":
case "auth/user-disabled":
case "auth/user-not-found":
setEmailError(err.message);
break;
case "auth/wrong-password":
setPasswordError(err.message);
break;
}
})
}
handleSignup()
for when a user signs up:
const handleSignup = () => {
clearErrors();
fire
.auth()
.createUserWithEmailAndPassword(email, password)
.catch(err => {
// user firebase err code to determine kind of error
// and handle it accordingly
switch(err.code) {
case "auth/email-already-in-use":
case "auth/invalid-email":
setEmailError(err.message);
break;
case "auth/weak-password":
setPasswordError(err.message);
break;
}
})
}
handleLogout()
for when a user logs out:
const handleLogout = () => {
fire.auth().signOut();
}
authListener()
for updating user state:
const authListener = () => {
fire.auth().onAuthStateChanged(user => {
if (user) {
clearInputs();
setUser(user);
} else {
setUser('');
}
})
}
Also, call authListener()
with a useEffect()
:
useEffect(() => {
authListener();
}, [])
At this point, /src/App.js
should look something like this:
import React, { useState, useEffect } from 'react'
import fire from './fire'
const App () => {
const [user, setUser] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');
const [hasAccount, setHasAccount] = useState(false);
const clearInputs = () => {
setEmail('');
setPassword('');
}
const clearErrors = () => {
setEmailError('');
setPasswordError('');
}
const handleLogin = () => {
clearErrors();
fire
.auth()
.signInWithEmailAndPassword(email, password)
.catch(err => {
// use firebase err code to determine kind of error
// and handle it accordingly
switch(err.code) {
case "auth/invalid-email":
case "auth/user-disabled":
case "auth/user-not-found":
setEmailError(err.message);
break;
case "auth/wrong-password":
setPasswordError(err.message);
break;
}
})
}
const handleSignup = () => {
clearErrors();
fire
.auth()
.createUserWithEmailAndPassword(email, password)
.catch(err => {
// use firebase err code to determine kind of error
// and handle it accordingly
switch(err.code) {
case "auth/email-already-in-use":
case "auth/invalid-email":
setEmailError(err.message);
break;
case "auth/weak-password":
setPasswordError(err.message);
break;
}
})
}
const handleLogout = () => {
fire.auth().signOut();
}
const authListener = () => {
fire.auth().onAuthStateChanged(user => {
if (user) {
clearInputs();
setUser(user);
} else {
setUser('');
}
})
}
useEffect(() => {
authListener();
}, [])
...
}
Create a component for the login page. For example, /src/components/Login.js
.
import React from 'react'
const Login = (props) => {
const {
email,
setEmail,
password,
setPassword,
handleLogin,
handleSignup,
hasAccount,
setHasAccount,
emailError,
passwordError
} = props;
return (
<section>
<div>
<label>Username</label>
<input type="text" autoFocus required value={email} onChange={e => setEmail(e.target.value)}/>
<p>{emailError}</p>
<label>Password</label>
<input type="text" required value={password} onChange={e => setPassword(e.target.value)}/>
<p>{passwordError}</p>
<div>
{ hasAccount ? (
<>
<button onClick={() => handleLogin()}>Sign In</button>
<p>Don't have an account? <span onClick={setHasAccount(!hasAccount)}>Sign up</span></p>
</>
) : (
<>
<button onClick={() => handleSignup()}>Sign Up</button>
<p>Already have an account? <span onClick={setHasAccount(!hasAccount)}>Sign in</span></p>
</>
)}
</div>
</div>
</section>
)
}
export default Login;
The above component is the barebones code you need to handle user signups and logins. You will have to add your own styling.
In /src/App.js
, import the Login component:
import Login from ./components/Login
And in the function for the App component, return the Login component conditionally on user:
const App = () => {
...
return (
{hasAccount ? (
<Login
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
handleLogin={handleLogin}
handleSignup={handleSignup}
hasAccount={hasAccount}
setHasAccount={setHasAccount}
emailError={emailError}
passwordError={passwordError}
/>
) : (
)}
)
}
This tutorial does not (yet) include how to store user ID's in a database, nor does it show how to handle welcome and forgot password emails. I plan on adding those in the future, as I have time and understand Firebase authentication better.
If you notice an error in this blog post, leave a comment and I will address it. Thank you!
For a quick copy/paste.
/src/App.js
import React, { useState, useEffect } from 'react'
import fire from './fire'
const App () => {
const [user, setUser] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');
const [hasAccount, setHasAccount] = useState(false);
const clearInputs = () => {
setEmail('');
setPassword('');
}
const clearErrors = () => {
setEmailError('');
setPasswordError('');
}
const handleLogin = () => {
clearErrors();
fire
.auth()
.signInWithEmailAndPassword(email, password)
.catch(err => {
// use firebase err code to determine kind of error
// and handle it accordingly
switch(err.code) {
case "auth/invalid-email":
case "auth/user-disabled":
case "auth/user-not-found":
setEmailError(err.message);
break;
case "auth/wrong-password":
setPasswordError(err.message);
break;
}
})
}
const handleSignup = () => {
clearErrors();
fire
.auth()
.createUserWithEmailAndPassword(email, password)
.catch(err => {
// use firebase err code to determine kind of error
// and handle it accordingly
switch(err.code) {
case "auth/email-already-in-use":
case "auth/invalid-email":
setEmailError(err.message);
break;
case "auth/weak-password":
setPasswordError(err.message);
break;
}
})
}
const handleLogout = () => {
fire.auth().signOut();
}
const authListener() => {
fire.auth().onAuthStateChanged(user => {
if (user) {
clearInputs();
setUser(user);
} else {
setUser('');
}
})
}
useEffect(() => {
authListener();
}, [])
return (
{hasAccount ? (
<Login
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
handleLogin={handleLogin}
handleSignup={handleSignup}
hasAccount={hasAccount}
setHasAccount={setHasAccount}
emailError={emailError}
passwordError={passwordError}
/>
) : (
)}
)
}
/src/components/Login.js
import React from 'react'
const Login = (props) => {
const {
email,
setEmail,
password,
setPassword,
handleLogin,
handleSignup,
hasAccount,
setHasAccount,
emailError,
passwordError
} = props;
return (
<section>
<div>
<label>Username</label>
<input type="text" autoFocus required value={email} onChange={e => setEmail(e.target.value)}/>
<p>{emailError}</p>
<label>Password</label>
<input type="text" required value={password} onChange={e => setPassword(e.target.value)}/>
<p>{passwordError}</p>
<div>
{ hasAccount ? (
<>
<button onClick={() => handleLogin()}>Sign In</button>
<p>Don't have an account? <span onClick={setHasAccount(!hasAccount)}>Sign up</span></p>
</>
) : (
<>
<button onClick={() => handleSignup()}>Sign Up</button>
<p>Already have an account? <span onClick={setHasAccount(!hasAccount)}>Sign in</span></p>
</>
)}
</div>
</div>
</section>
)
}
export default Login;
15