Ashlee standing in front of a brick wall looking up to her left and smiling. Her right hand is held up near her right shoulder with the palm facing upwards.

Ashlee M Boyer

You can find me talking about issues surrounding Disability, Accessibility, & Mental Health on Twitter, or you can find me regularly live-knitting or live-coding on Twitch.

Build a Blog Site with Next.js and Firebase Part 4 - Adding Authentication

Part of the Build a Blog Site with Next.js and Firebase Series

Tuesday, January 26, 2021 — 9 minute read

#react#nextjs#firebase#web-development

Welcome to the fourth post in my new "Build a Blog Site with Next.js and Firebase" series! This series is pretty similar to a series I wrote in 2019: "Build a React & Firebase Blog Site". Because it's been well over a year since I published that series, I decided to create a new series and use the Next.js React framework this time. It's a fun framework to use, and I know so many people that are curious about it. I hope you enjoy the series!

Read Part 3 of the "Build a Blog Site with Next.js and Firebase" series

Part 5 of this series will be published one week from today, Monday, February 1st, 2021. If you'd like to get an email notification when that happens, consider subscribing to my newsletter at ashleemboyer.com/newsletter. As always, you can contact me through Twitter or email if you run into any issues.

Table of Contents


The importance of authentication in this app is making it so only you can create, edit, and delete posts. It is your blog after all. We'll use Firebase Authentication to manage a user account and we'll track the authentication state using React Context. Then we can hide certain parts of the app from people who aren't logged in.

Set up Authentication in the Firebase Console

Firebase makes it easy for us to set up email/password authentication in our app. It's also easy to add a user for ourselves right from the console. Make sure you use a real email address and a secure password for your user. This is how you're protecting your blog! There's a video after these steps that visually walks though how to do this.

  1. Go to the Firebase console for your project.
  2. Go to the "Authentication" page under "Build".
  3. Click "Get Started".
  4. You should be on the "Sign-in method" tab.
  5. Click "Email/Password".
  6. Enable the first toggle.
  7. Click "Save".
  8. Switch to the "Users" tab.
  9. Click "Add user".
  10. Enter a secure email and password.
  11. Click "Add user".
  1. Go to your Realtime Database.
  2. Click the "Rules" tab.
  3. Update the rules to allow all reads but only authenticated writes:
json
123456
{  "rules": {    ".read": true,    ".write": "auth != null"  }}
  1. Click the "Publish" button.

Manage Authentication State with React Context

  1. Import firebase/auth into lib/firebase.js after the other Firebase imports:
js
1
import 'firebase/auth';
  1. Add an onAuthStateChanged function to lib/firebase.js:
js
12345678910
/*Observes changes in authentication. Receives a callback function that is invokedwhen auth state changes. See the Firebase Reference Docs for all of the details:https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged*/export const onAuthStateChanged = async (callback) => {  initFirebase();   return firebase.auth().onAuthStateChanged((user) => callback(user));};
  1. Add a contexts directory at the root of the project.
  2. Update jsconfig.json for the new contexts directory:
json
1234567891011
{  "compilerOptions": {    "baseUrl": "./",    "paths": {      "@components": ["components"],      "@contexts/*": ["contexts/*"],      "@lib/*": ["lib/*"],      "@styles/*": ["styles/*"]    }  }}
  1. Add an auth.js file to contexts:
js
123456789101112131415161718192021222324
import { createContext, useState, useEffect, useContext } from 'react';import { onAuthStateChanged } from '@lib/firebase'; const AuthContext = createContext({ user: null, userLoading: true }); export const AuthProvider = ({ children }) => {  const [userLoading, setUserLoading] = useState(true);  const [user, setUser] = useState();   useEffect(() => {    return onAuthStateChanged((res) => {      setUser(res);      setUserLoading(false);    });  }, []);   return (    <AuthContext.Provider value={[user, userLoading]}>      {children}    </AuthContext.Provider>  );}; export const useAuth = () => useContext(AuthContext);
  1. Update pages/_app.js to use AuthProvider:
js
12345678910
import { AuthProvider } from '@contexts/auth';import '@styles/global.scss'; const App = ({ Component, pageProps }) => (  <AuthProvider>    <Component {...pageProps} />  </AuthProvider>); export default App;
  1. Restart your development server if it's running.
  2. Make sure your home page loads without error.
  3. Commit and push your work to your repository:
123
git add .git commit -m "Adding AuthContext"git push

Check for an Authenticated User in CreatePage

  1. Import useAuth in pages/create.js:
jsx
1
import { useAuth } from '@contexts/auth';
  1. Invoke it in the component after the isLoading variable and log the results.
jsx
12
const [user, userLoading] = useAuth();console.log(user, userLoading);
  1. Visit http://localhost:3000/create in your browser.
  2. You should see three logs in the console:
123
undefined truenull truenull false

The first shows the initial values of user and userLoading when we invoke useAuth. The second shows those values after the callback we pass into onAuthStateChanged has been invoked and setUser has been called. The third shows after setUserLoading has been called in the same callback. This is why we call setUser before setUserLoading, so we can ensure that everything is truly loaded before changing that state.

  1. Redirect to 404 if no user and return null if user is loading, before the handleChange and handleSubmit functions are defined. This placement prevents us from unecessarily defining those two functions if we're not going to use them.
jsx
12345678
if (userLoading) {  return null;} if (!user && typeof window !== 'undefined') {  router.push('/404');  return null;}
  1. Visit http://localhost:3000/create in your browser.
  2. You should be redirected to the 404 page.
  3. Commit and push your work to your repository:
123
git add .git commit -m "Requiring Authentication in CreatePage"git push

Add a Page for Signing In

  1. Add a new signIn function to lib/firebase.js:
js
12345678
/*Attempts to authenticate a user with a given email and password.*/export const signIn = async (email, password) => {  initFirebase();   return firebase.auth().signInWithEmailAndPassword(email, password);};
  1. Add styles/signin.module.scss:
scss
12345678910111213141516171819202122232425262728
.SignIn {  max-width: 500px;  margin: 32px auto;  padding: 32px;  background-color: white;  border-radius: 4px;  box-shadow: 0 2px 6px rgba(black, 0.3);   form {    h1 {      margin: 0;      margin-bottom: 24px;      text-align: center;    }     input {      margin-bottom: 16px;    }     button {      min-width: 120px;      display: block;      margin-top: 32px;      margin-right: auto;      margin-left: auto;    }  }}
  1. Add pages/signin.js:
jsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
import { useState } from 'react';import { useRouter } from 'next/router';import { signIn } from '@lib/firebase';import { useAuth } from '@contexts/auth';import styles from '@styles/signin.module.scss'; const SignInPage = () => {  const router = useRouter();  const [user, userLoading] = useAuth();  const [values, setValues] = useState({ email: '', password: '' });   if (userLoading) {    return <h1>Loading...</h1>;  }   if (user && typeof window !== 'undefined') {    router.push('/');    return null;  }   const handleChange = (e) => {    const id = e.target.id;    const newValue = e.target.value;     setValues({ ...values, [id]: newValue });  };   const handleSubmit = (e) => {    e.preventDefault();     let missingValues = [];    Object.entries(values).forEach(([key, value]) => {      if (!value) {        missingValues.push(key);      }    });     if (missingValues.length > 1) {      alert(`You're missing these fields: ${missingValues.join(', ')}`);      return;    }     signIn(values.email, values.password).catch((err) => {      alert(err);    });  };   return (    <div className={styles.SignIn}>      <form onSubmit={handleSubmit}>        <h1>Please Sign In</h1>        <label htmlFor="email">Email</label>        <input          id="email"          type="email"          value={values.email}          onChange={handleChange}        />        <label htmlFor="password">Password</label>        <input          id="password"          type="password"          value={values.password}          onChange={handleChange}        />        <button type="submit">Sign In</button>      </form>    </div>  );}; export default SignInPage;
  1. Go to http://localhost:3000/signin in your browser.
  2. Try logging in with the user credentials you created at the beginning of this post.
  3. If successful, you should be sent to the home page.
  4. Go to http://localhost:3000/create in your browser. You should not be redirected to the 404 page.
  5. Commit and push your work to your repository:
123
git add .git commit -m "Adding SignInPage"git push

Add a Sign Out Button to Layout

  1. Add a new signOut function to lib/firebase.js:
js
12345678
/*Signs out the authenticated user.*/export const signOut = async () => {  initFirebase();   return firebase.auth().signOut();};
  1. Update the Layout component to check for a user and render a Sign Out button if there is one.
jsx
12345678910111213141516171819202122232425
import { signOut } from '@lib/firebase';import { useAuth } from '@contexts/auth';import styles from './Layout.module.scss'; const Layout = ({ children }) => {  const [user] = useAuth();   return (    <div className={styles.Layout}>      <nav>        <span>          <a href="/">My Next.js Blog</a>        </span>        {user && (          <span>            <button onClick={() => signOut()}>Sign Out</button>          </span>        )}      </nav>      <main>{children}</main>    </div>  );}; export default Layout;
  1. Update the Layout styles so the <nav> element uses Flexbox and the new button stands out from the blue bar.
scss
12345678910111213141516171819
.Layout {  nav {    display: flex;    justify-content: space-between;    align-items: center;    ...     span {      ...       button {        color: #1a73e8;        background-color: white;      }    }  }   ...}
  1. Go to http://localhost:3000/ in your browser.
  2. In the blue bar at the top, you should see a white button with blue text that reads "Sign Out" like this:

  1. Click the Sign Out button and check that you can no longer access the create page at http://localhost:3000/create.
  2. Commit and push your work to your repository:
123
git add .git commit -m "Adding Sign Out button to Layout"git push
  1. Celebrate!!! You did it!!! ğŸŽ‰