skip to Main Content

Note: I’m using Next.js 13 with the app/ directory.


I’m learning Firebase and Next.js and I’m struggling to understand how to solve a toy problem. Suppose I have a Home() component like this

/app/page.jsx

export default function Home() {
  return (
    <main>
      <h1>Hello World</h1>
      <p>This text should only be visible to authenticated users</p>
    </main>
  )
}

My goal is to conditionally render everything in the <p>...</p> based on whether the user who requested the page is a logged in user. Firebase uses JWT, and Next.js 13 renders this component server side, so I believe this should be possible, but I can’t figure out how to do it.

I’m aware of onAuthStateChanged, but to my knowledge, this can only be used client-side. (A savvy user could still view this protected content.) How do I protect this content, server side?

2

Answers


  1. To check if the user is logged in you can use ‘onAuthStateChanged’ method.

    Store this information in the component’s state or use it to conditionally render parts of the component.

    import React, { useEffect, useState } from 'react';
    import firebase from 'firebase/app';
    import 'firebase/auth';
    
    export default function Home() {
      const [user, setUser] = useState(null);
    
      useEffect(() => {
        const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      setUser(user);
      });
        return () => unsubscribe();
      }, []);
    
      return (
        <main>
          <h1>Hello World</h1>
          {user ? (
            <p>authenticated user</p>
          ) : null}
        </main>
      );
    }
    

    To perform user authentication on server side Next.js gives us ‘getServerSideProps’ to fetch the user’s authentication status

    import firebase from 'firebase/app';
    import 'firebase/auth';
    
    export default function Home({ user }) {
      return (
        <main>
          <h1>Hello World</h1>
          {user ? (
            <p>authenticated user</p>
          ) : null}
        </main>
      );
    }
    
    export async function getServerSideProps(context) {
      const user = await new Promise((resolve, reject) => {
        firebase.auth().onAuthStateChanged(user => {
          resolve(user);
        });
      });
    
      return {
        props: {
          user,
        },
      };
    }
    

    updated solution:

    Create a server-side route

    import firebase from 'firebase/app';
    import 'firebase/auth';
    import express from 'express';
    
    const app = express();
    
    app.get('/api/user', async (req, res) => {
      const user = await new Promise((resolve, reject) => {
        firebase.auth().onAuthStateChanged(user => {
          resolve(user);
        });
      });
    
      res.send(user);
    });
    
    app.listen(3000, () => {
      console.log('Server started at http://localhost:3000');
    });
    

    Client side

    import React, { useState, useEffect } from 'react';
    import axios from 'axios';
    
    export default function Home() {
      const [user, setUser] = useState(null);
    
      useEffect(() => {
        const fetchUser = async () => {
          const res = await axios.get('http://localhost:3000/api/user');
          const user = res.data;
    
          setUser(user);
        };
    
        fetchUser();
      }, []);
    
      return (
        <main>
          <h1>Hello World</h1>
          {user ? (
            <p>authenticated user</p>
          ) : null}
        </main>
      );
    }
    
    Login or Signup to reply.
  2. I would recommend using NextAuth with Firebase adapter (https://next-auth.js.org/adapters/firebase).

    Simply create middleware to catch/filter authenticated paths:

    // middleware.ts on the same level as your api/pages directory
    import { withAuth } from "next-auth/middleware"
    
    export const config = { matcher: ["/dashboard/:path*"] }
    export default withAuth({})
    

    then follow the NextAuth documentation by creating an api route under:
    /pages/api/auth/[...nextauth].ts (at the time of writing it is not possible to create apis under the app directory)

    import NextAuth from "next-auth"
    import GoogleProvider from "next-auth/providers/google"
    import { FirestoreAdapter } from "@next-auth/firebase-adapter"
    
    // For more information on each option (and a full list of options) go to
    // https://next-auth.js.org/configuration/options
    export default NextAuth({
      // https://next-auth.js.org/providers
      providers: [
        GoogleProvider({
          clientId: process.env.GOOGLE_ID,
          clientSecret: process.env.GOOGLE_SECRET,
        }),
      ],
      adapter: FirestoreAdapter({
        apiKey: process.env.FIREBASE_API_KEY,
        appId: process.env.FIREBASE_APP_ID,
        authDomain: process.env.FIREBASE_AUTH_DOMAIN,
        databaseURL: process.env.FIREBASE_DATABASE_URL,
        projectId: process.env.FIREBASE_PROJECT_ID,
        storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
        messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
        // Optional emulator config (see below for options)
        emulator: {},
      }),
      // ...
    });
    

    optionally you can also use useSession to render individual elements based on if the user is logged in or not like this:

    import { useSession, signOut } from "next-auth/react"
    ...
    export default function LoginButton() {
      const { data: session } = useSession()
    
      if (session) {
        return (
          <div className={"flex items-center gap-3"}>
            <button onClick={() => signOut()}>Sign out</button>
            <Link  href={"/workspace"}>Dashboard</Link>
          </div>
        )
      }
      ...
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search