π Setting up Firebase Authentication with Next.js
Still Hand-rolling your own authentication? How original.
Use protection π‘
By that, I mean... don't try to write your own auth if you are serious about your website.
I mean, a great exercise, but a total waste of time and there's a 99% chance it will be full of f**king holes. You wouldn't be reading this post if you could get it 100% because we're all just normies here.
Firebase is Google's way of saying, leave this to the people with an IQ over 50 (and give us your bloody money). This is why I don't work at Google.
Fear not, the free tier is so damn good, you will only have to start paying when you have serious traffic.
Why Use Firebase Authentication? π
Reasons to use Firebase Auth:
Minimal server setup: Because frontend dev no talk backend.
Multiple auth providers: Let users sign in with whatever data-leaking platform they prefer. Google, Facebook, wait Meta, Twitter, no, X? β and a bunch of other major ones who are probably changing their name as we speak.
Scalability: Whether it's 10 users or 10 million, it's Google, they can handle it. Probably.
Security: Built by Google, so you know they won't let anyone get your data. Unless they're paying for it.
Reasons not to use Firebase Auth:
You enjoy reinventing the wheel, square-shaped, of course.
You hate time (money).
Righto my loyal, low IQ companions. Code π§
Let's integrate Firebase Authentication into a Next.js app. If you're using something else, well, you probably also enjoy using spaces instead of tabs. The principles are similar, but you'll be on your own.
Step 1: Install Firebase packages
Install the Firebase client and admin SDKs.
1npm install firebase firebase-admin
Step 2: Set up Firebase project
Head over to the Firebase Console and create a new project. Don't worry; they won't ask you to pee in a cup. You'll also want to create a Web App in your project settings.
Step 3: Get your Firebase config
In your Firebase project settings, locate your web app's Firebase configuration. It'll look something like this:
1// firebaseConfig.js
2
3export const firebaseConfig = {
4 apiKey: "your-api-key",
5 authDomain: "your-app.firebaseapp.com",
6 projectId: "your-project-id",
7 // ...other config
8};
Step 4: Initialise Firebase on the Client
Create a firebaseClient.js
file to initialise Firebase on the client side.
1// lib/firebaseClient.js
2
3import { initializeApp, getApps } from "firebase/app";
4import { getAuth } from "firebase/auth";
5import { firebaseConfig } from "./firebaseConfig";
6
7let firebaseApp;
8if (!getApps().length) {
9 firebaseApp = initializeApp(firebaseConfig);
10}
11
12export const auth = getAuth(firebaseApp);
Step 5: Initialize Firebase Admin on the server
Create a firebaseAdmin.js
file to initialise Firebase Admin SDK.
1// lib/firebaseAdmin.js
2
3import { initializeApp, getApps, cert } from "firebase-admin/app";
4import { getAuth } from "firebase-admin/auth";
5
6const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY || "{}");
7
8if (!getApps().length) {
9 initializeApp({
10 credential: cert(serviceAccount),
11 });
12}
13
14export const adminAuth = getAuth();
Wait, wtf is FIREBASE_SERVICE_ACCOUNT_KEY
?
Remain calm, go back to your Firebase console:
Navigate to Project Settings > Service Accounts.
Click Generate New Private Key.
Save the JSON file and add its contents to your
.env.local
file:
1FIREBASE_SERVICE_ACCOUNT_KEY='{
2 "type": "service_account",
3 "project_id": "your-project-id",
4 // ...rest of the JSON
5}'
Yes, it's a big environment variable. No, it's not my fault. You might wanna one-line it.
Step 6: Create a sign-in function
Let's allow users to sign in with Google because we've been trained into enjoying handing over more data to Google.
1// pages/login.js
2
3import { useEffect } from "react";
4import { auth } from "../lib/firebaseClient";
5import { GoogleAuthProvider, signInWithPopup } from "firebase/auth";
6import { useRouter } from "next/router";
7
8export default function Login() {
9 const router = useRouter();
10
11 const signInWithGoogle = async () => {
12 const provider = new GoogleAuthProvider();
13 try {
14 const result = await signInWithPopup(auth, provider);
15 const idToken = await result.user.getIdToken();
16
17 // Send token to server
18 await fetch("/api/login", {
19 method: "POST",
20 headers: {
21 "Content-Type": "application/json",
22 },
23 body: JSON.stringify({ idToken }),
24 });
25
26 router.push("/dashboard");
27 } catch (error) {
28 console.error("Authentication error", error);
29 }
30 };
31
32 return (
33 <div>
34 <h1>Login</h1>
35 <button onClick={signInWithGoogle}>Sign in with Google</button>
36 </div>
37 );
38}
39
Step 7: Create the login API route
Now, let's handle the login on the server side.
1// pages/api/login.js
2
3import { adminAuth } from "../../lib/firebaseAdmin";
4import cookie from "cookie";
5
6export default async function handler(req, res) {
7 const { idToken } = req.body;
8
9 try {
10 const decodedToken = await adminAuth.verifyIdToken(idToken);
11
12 // Create a session cookie
13 const sessionCookie = await adminAuth.createSessionCookie(idToken, {
14 expiresIn: 60 * 60 * 24 * 5 * 1000, // 5 days
15 });
16
17 // Set cookie policy for session cookie
18 const options = {
19 maxAge: 60 * 60 * 24 * 5,
20 httpOnly: true,
21 secure: process.env.NODE_ENV === "production",
22 path: "/",
23 };
24
25 res.setHeader("Set-Cookie", cookie.serialize("session", sessionCookie, options));
26 res.status(200).end();
27 } catch (error) {
28 console.error("Login error", error);
29 res.status(401).send("Unauthorized");
30 }
31}
Step 8: Protect your routes with middleware
Because we can't trust anyone these days.
1// middleware.js
2
3import { NextResponse } from "next/server";
4import { adminAuth } from "./lib/firebaseAdmin";
5
6export async function middleware(req) {
7 const sessionCookie = req.cookies.get("session")?.value || "";
8
9 try {
10 await adminAuth.verifySessionCookie(sessionCookie, true);
11 return NextResponse.next();
12 } catch (error) {
13 return NextResponse.redirect(new URL("/login", req.url));
14 }
15}
16
17export const config = {
18 matcher: ["/dashboard/:path*"],
19};
Step 9: Create a protected page
Let's make a dashboard page that only logged-in users can access.
1// pages/dashboard.js
2
3export default function Dashboard() {
4 return (
5 <div>
6 <h1>Welcome to your dashboard!</h1>
7 <p>Only logged-in users can see this.</p>
8 </div>
9 );
10}
Step 10: Implement sign-out functionality
All good things must come to an end. Hopefully you made some money before the end.
1// components/SignOutButton.js
2
3import { auth } from "../lib/firebaseClient";
4import { signOut } from "firebase/auth";
5import { useRouter } from "next/router";
6
7export default function SignOutButton() {
8 const router = useRouter();
9
10 const handleSignOut = async () => {
11 await signOut(auth);
12 // Destroy session cookie
13 await fetch("/api/logout", {
14 method: "POST",
15 });
16 router.push("/login");
17 };
18
19 return <button onClick={handleSignOut}>Sign Out</button>;
20}
And API side:
1// pages/api/logout.js
2
3import cookie from "cookie";
4
5export default async function handler(req, res) {
6 res.setHeader(
7 "Set-Cookie",
8 cookie.serialize("session", "", {
9 maxAge: -1,
10 path: "/",
11 })
12 );
13 res.status(200).end();
14}
Done! π
You've just integrated Firebase Authentication into your Next.js app. Now you can start pretending to know what you are talking about like I do.
But wait, there's an even easier way π£
Feeling overwhelmed? Wondering if there's a shortcut that doesn't involve selling your soul? Well, you're in luck, about the overwhelmed part. Kinda have to sell your soul nowadays if you want to make it.
I made a template.
Introducing the ShipNow Boost Template β your all-in-one Next.js starter kit with Firebase Authentication (and a boatload of other goodies) already baked in.
What's Inside? π¦
π₯ Firebase Auth Pre-Integrated: Sign-up, sign-in, sign-out β all set up for you.
π₯ Firebase Firestore Pre-Integrated: A document based database ready to go.
π Tailwind CSS Styling: Make your app look like a million bucks without lifting a finger.
πΈ Stripe Integration: Because who doesn't like making money?
π§ Mailgun Integration: Send emails without ending up in the spam folder.
π‘ TypeScript Support: Catch errors before they catch you.
π Storyblok CMS: Manage your content without pulling your hair out.
And so much more...
Stop wasting time on boilerplate β³
Time is money, and you're burning both by doing everything from scratch. With the ShipNow Boost Template, you can jump straight into building features that will send your product to the stratosphere.
Final thoughts π€
Authentication doesn't have to be a nightmare. Whether you choose to add Auth yourself or use our ShipNow Boost Template, the goal is to get your product to market without losing your sanity... or hopefully people's data.
So go ahead, take the leap get the template or check out this post if you really need me to sell you on it.
Want more?
Get notified about new posts and updates. No spam, I promise.
We will never share nor sell your details.