Archived Build a Blog Site with Next.js and Firebase Part 4 - Adding Authentication
Archived
🚨 ATTENTION 🚨
You are currently viewing an archived post. The information here may no longer be accurate or maybe Ashlee just decided not to publish the post as publicly anymore.
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 1 of the series
- Read Part 2 of the series
- Read Part 3 of the series
- Read Part 5 of the series
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.
- Go to the Firebase console for your project.
- Go to the “Authentication” page under “Build”.
- Click “Get Started”.
- You should be on the “Sign-in method” tab.
- Click “Email/Password”.
- Enable the first toggle.
- Click “Save”.
- Switch to the “Users” tab.
- Click “Add user”.
- Enter a secure email and password.
- Click “Add user”.
- Go to your Realtime Database.
- Click the “Rules” tab.
- Update the rules to allow all reads but only authenticated writes:
{
"rules": {
".read": true,
".write": "auth != null"
}
}
- Click the “Publish” button.
Manage Authentication State with React Context
- Import
firebase/auth
intolib/firebase.js
after the other Firebase imports:
import 'firebase/auth';
- Add an
onAuthStateChanged
function tolib/firebase.js
:
/*
Observes changes in authentication. Receives a callback function that is invoked
when 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));
};
- Add a
contexts
directory at the root of the project. - Update
jsconfig.json
for the newcontexts
directory:
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@components": ["components"],
"@contexts/*": ["contexts/*"],
"@lib/*": ["lib/*"],
"@styles/*": ["styles/*"]
}
}
}
- Add an
auth.js
file tocontexts
:
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);
- Update
pages/_app.js
to useAuthProvider
:
import { AuthProvider } from '@contexts/auth';
import '@styles/global.scss';
const App = ({ Component, pageProps }) => (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
);
export default App;
- Restart your development server if it’s running.
- Make sure your home page loads without error.
- Commit and push your work to your repository:
git add .
git commit -m "Adding AuthContext"
git push
Check for an Authenticated User in CreatePage
- Import
useAuth
inpages/create.js
:
import { useAuth } from '@contexts/auth';
- Invoke it in the component after the
isLoading
variable and log the results.
const [user, userLoading] = useAuth();
console.log(user, userLoading);
- Visit
http://localhost:3000/create
in your browser. - You should see three logs in the console:
undefined true
null true
null 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.
- Redirect to 404 if no user and return null if user is loading, before the
handleChange
andhandleSubmit
functions are defined. This placement prevents us from unecessarily defining those two functions if we’re not going to use them.
if (userLoading) {
return null;
}
if (!user && typeof window !== 'undefined') {
router.push('/404');
return null;
}
- Visit
http://localhost:3000/create
in your browser. - You should be redirected to the 404 page.
- Commit and push your work to your repository:
git add .
git commit -m "Requiring Authentication in CreatePage"
git push
Add a Page for Signing In
- Add a new
signIn
function tolib/firebase.js
:
/*
Attempts to authenticate a user with a given email and password.
*/
export const signIn = async (email, password) => {
initFirebase();
return firebase.auth().signInWithEmailAndPassword(email, password);
};
- Add
styles/signin.module.scss
:
.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;
}
}
}
- Add
pages/signin.js
:
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;
- Go to
http://localhost:3000/signin
in your browser. - Try logging in with the user credentials you created at the beginning of this post.
- If successful, you should be sent to the home page.
- Go to
http://localhost:3000/create
in your browser. You should not be redirected to the 404 page. - Commit and push your work to your repository:
git add .
git commit -m "Adding SignInPage"
git push
Add a Sign Out Button to Layout
- Add a new
signOut
function tolib/firebase.js
:
/*
Signs out the authenticated user.
*/
export const signOut = async () => {
initFirebase();
return firebase.auth().signOut();
};
- Update the
Layout
component to check for a user and render a Sign Out button if there is one.
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;
- Update the
Layout
styles so the<nav>
element uses Flexbox and the new button stands out from the blue bar.
.Layout {
nav {
display: flex;
justify-content: space-between;
align-items: center;
...
span {
...
button {
color: #1a73e8;
background-color: white;
}
}
}
...
}
- Go to
http://localhost:3000/
in your browser. - In the blue bar at the top, you should see a white button with blue text that reads “Sign Out” like this:
- Click the Sign Out button and check that you can no longer access the create page at
http://localhost:3000/create
. - Commit and push your work to your repository:
git add .
git commit -m "Adding Sign Out button to Layout"
git push
- Celebrate!!! You did it!!! 🎉