DEV Community

ariburaco
ariburaco

Posted on • Originally published at mystickynotes.net

Authentication with Firebase in NextJS, with SSR!

Introduction

In this post, we will learn how to set up user authentication in a Next.js application using Firebase. Firebase is a popular backend-as-a-service provider that offers a variety of tools for building web and mobile applications, including user authentication. By the end of this tutorial, you will have a basic understanding of how to use Firebase to authenticate users in your Next.js application, allowing them to sign in, sign up, and sign out.

Getting Started

The first step will create a project under firebase console, once you create a web project, firebase will provide you a code snippet to use on the client side.

// Import the functions you need from the SDKs you need
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional

const firebaseConfig = {
 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
 storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
 messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
 appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
 measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};


// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);

Enter fullscreen mode Exit fullscreen mode

Client Side Authentication

Once you initialize the firebase app, you are good to go!

Firebase has a lot of providers for authentication, like social logins with Google, Github, Apple, Facebook and etc. Also, you can choose a simple email and password login. In this example, I’ll create the users manually, since only admin users will be able to log in.

After selecting the provider and creating the users, we can start building our sign-in logic.


 import { auth } from 'configs/firebase-config';
 import { onIdTokenChanged, User } from 'firebase/auth';

 const [admin, setAdmin] = useState<User | null>(null);

 useEffect(() => {
   const unsubscribe = onIdTokenChanged(auth, async (user) => {
     if (user) {
       setAdmin(user);
     } else {
       setAdmin(null);
     }
   });

   return unsubscribe;
 }, []);
Enter fullscreen mode Exit fullscreen mode

So, we will use this useEffect hook on the login page, if someone logs in, it automatically assigns the user to our state. And this is the actual login function:

import { signInWithEmailAndPassword } from 'firebase/auth';

 const [email, setEmail] = useState('');
 const [password, setPassword] = useState('');

 const signInwithFirebase = async () => {
   try {
     await signInWithEmailAndPassword(auth, email, password);
   } catch (error: FirebaseError | unknown) {
     if (error instanceof FirebaseError) {
        console.log(code);
     }
   }
 };

Enter fullscreen mode Exit fullscreen mode

That’s it! Now you are authenticated with Firebase Auth. After this point, you can create a Context Provider to store the Auth User in the whole application.

There is one caveat in this approach and that is SSR. Since we are checking the authentication on the client side, when we first load the page, we might get some flickery behaviors. But wait, there is a solution for this also!

Server Side Authentication - Power of the “getServerSideProps”

So, the basic idea is that we need to validate the authenticated user on the backend server before loading the page. Able to do this, we need another library called firebase-admin, with this library we are able to do a lot of things such as creating users, databases, and validations.

Here is the basic configuration of the firebase-admin app:


import * as admin from 'firebase-admin';

/* eslint-disable import/prefer-default-export */
import { credential } from 'firebase-admin';
import { initializeApp } from 'firebase-admin/app';

const serviceAccount = require('ServiceAccountFile.json');

if (!admin.apps.length) {
 initializeApp({
   credential: credential.cert(serviceAccount),
 });
}

export const adminSDK = admin;

Enter fullscreen mode Exit fullscreen mode

Once we configured the adminSDK, we can use it on our application.

So, the logic of the server side auth verification is that we need to validate the users token on the backend. So, once we are logged in as a user, firebase gives us a unique user token. Somehow we need to pass this token value to our backend and the best way to do this cookies!

Here is the implementation of the server side validation of the authenticated user:

import nookies from 'nookies';
import { adminSDK } from 'configs/firebase-admin-config';

export async function getServerSideProps(ctx: GetServerSidePropsContext) {
 const cookies = nookies.get(ctx);
 if (!cookies.token) {
   return {
     props: {
       isLoggedIn: false,
     },
   };
 }

 try {
   const token = await adminSDK.auth().verifyIdToken(cookies.token);
   if (!token) {
     return {
       props: {
        isLoggedIn: false,
       },
     };
   }

   // the user is authenticated!
   const { uid } = token;
   const user = await adminSDK.auth().getUser(uid);

   return {
     props: {
       isLoggedIn: true,
     },
   };
 } catch (error) {
   return {
     props: {
       isLoggedIn: false,
     },
   };
 }
}

Enter fullscreen mode Exit fullscreen mode

And of course, once we logged on the client side we need to store the user token in the cookies. So, the client side implementation will be change a little bit.

useEffect(() => {
   const unsubscribe = onIdTokenChanged(auth, async (user) => {
     if (user) {
       setAdmin(user);
       const token = await user.getIdToken();
       nookies.set(undefined, 'token', token, { path: '/' });
     } else {
       setAdmin(null);
       nookies.set(undefined, 'token', '', { path: '/' });
     }
   });

   return unsubscribe;
 }, []);

Enter fullscreen mode Exit fullscreen mode

Conclusion

That’s it! Now, you have both client and server-side authentication. Using this approach you can create excellent web applications.
We have learned how to set up user authentication in a Next.js application using Firebase. We have seen how Firebase makes it easy to handle user registration and login, and how to use Firebase's authentication API to authenticate users in our application. We also saw how to use Firebase's Firestore to store user data and how to protect routes based on the authenticated user. With this knowledge, you should now be able to add user authentication to your Next.js applications easily and securely. Happy hacking!

Top comments (5)

Collapse
 
hadnazzar profile image
Melih Yumak

Great article! I believe nextjs and server side rendering will be much more popular than ever in the next years.
Thanks for sharing!

Collapse
 
jdgamble555 profile image
Jonathan Gamble

Shouldn't you use the built in cookies function here? - nextjs.org/docs/app/api-reference/...

Collapse
 
ariburaco profile image
ariburaco

Yes, you are right but I think the built-in cookies function is introduced with the app router.

Collapse
 
thanhtutzaw profile image
ThanHtutZaw

now I get firebase token expired in every 1hour . How can I solve it .

Collapse
 
ariburaco profile image
ariburaco
useEffect(() => {
    const handle = setInterval(async () => {
      if (auth.currentUser) {
        const token = await getIdToken(auth.currentUser);
        nookies.set(undefined, 'token', token, { path: '/' });
      } else {
        nookies.set(undefined, 'token', '', { path: '/' });
      }
    }, 10 * 60 * 1000);

    return () => clearInterval(handle);
  }, []);
Enter fullscreen mode Exit fullscreen mode

You can try something like this to refresh the token in every 10 mins. on the client side.