Authentication (Firebase)

Authenticate user and store in Firestore.

Authentication (Firebase)

Create a User Context

context.ts
import { createContext } from 'react'
export const UserContext = createContext({ user: null, authLoading: true })

Add the Provider in the App.js (Next.js _app.jsx file is used here)

_app.jsx
import 'regenerator-runtime/runtime'
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Script from 'next/script'
import { UserContext } from '../lib/context'
import { useUserData } from '../lib/hooks'

function MyApp({ Component, pageProps }: AppProps) {
  const userData = useUserData()
  return (
    <>
      <UserContext.Provider value={userData}>
        <Component {...pageProps} />
      </UserContext.Provider>
    </>
  )
}

export default MyApp

Initialize firebase app with YOUR configurations.

firebase.ts

import { initializeApp, getApps, getApp } from 'firebase/app'
import { getAuth, GoogleAuthProvider } from 'firebase/auth'
import {
  getFirestore,
  collection,
  where,
  getDocs,
  query,
  limit,
} from 'firebase/firestore'
import { getStorage } from 'firebase/storage'

// Get this from project settings (Settings -> Project settings -> Found at the bottom.)
const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_AUTH_DOMAIN',
  projectId: 'YOUR_PROJECT_ID',
  storageBucket: 'YOUR_STORAGE_BUCKET',
  messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
  appId: 'YOUR_APP_ID',
  measurementId: 'YOUR_MEASUREMENT_ID',
}
function createFirebaseApp(config) {
  try {
    return getApp()
  } catch {
    return initializeApp(config)
  }
}

const firebaseApp = createFirebaseApp(firebaseConfig)

export const auth = getAuth(firebaseApp)
export const googleAuthProvider = new GoogleAuthProvider()


export const firestore = getFirestore(firebaseApp)

// Storage exports
export const storage = getStorage(firebaseApp)
export const STATE_CHANGED = 'state_changed'

Hook to check for auth state change

hooks.ts
import { doc, onSnapshot, getFirestore } from 'firebase/firestore'
import { auth, firestore } from './firebase'
import { useEffect, useState } from 'react'
import { useAuthState } from 'react-firebase-hooks/auth'
import { useRouter } from 'next/router'

// Custom hook to read  auth record and user profile doc
export function useUserData() {
  const [user, loading] = useAuthState(auth)
  const router = useRouter()

  useEffect(() => {
    // turn off realtime subscription
    let unsubscribe

    if (user) {
      const ref = doc(getFirestore(), 'users', user.uid)
      unsubscribe = onSnapshot(ref, (doc) => {console.log('doc', doc)})
    }

    return unsubscribe
  }, [user])

  return { user, authLoading: loading }
}

When auth state changes - context updates and components rerender.

Use the logic in your protected routes and login components

login.tsx
import { signInWithPopup } from 'firebase/auth'
import { doc, getFirestore, getDoc, setDoc } from 'firebase/firestore'
import type { NextPage } from 'next'
import { useRouter } from 'next/router'
import { useContext, useEffect } from 'react'
import MetadataComponent from '../components/MetadataComponent'
import { UserContext } from '../lib/context'
import { auth, googleAuthProvider } from '../lib/firebase'

const Login: NextPage = () => {
  const router = useRouter()

  const { user, authLoading } = useContext(UserContext)
  useEffect(() => {
    if (!authLoading && user) {
      // user is already logged in.
      redirectToDashboard()
    }
  }, [user])
  const redirectToDashboard = () => {
    router.push('/dashboard')
  }

  const signInWithGoogle = async () => {
    signInWithPopup(auth, googleAuthProvider)
      .then((res) => {
        console.log('successful', res)

        writeUserToFirestore(res.user)
      })
      .catch((err) => {
        console.log('err', err)
      })
  }

  const writeUserToFirestore = async (currentUser) => {
    const userRef = doc(getFirestore(), 'users', currentUser.uid)
    const userDoc = await getDoc(userRef)

    if (!userDoc.exists()) {
      const userData = {
        displayName: currentUser.displayName,
        email: currentUser.email,
        photoURL: currentUser.photoURL,
        uid: currentUser.uid,
        isAdmin: false,
      }
      await setDoc(userRef, userData)
      redirectToDashboard()
    } else {
      console.log('user already exists...')
      redirectToDashboard()
    }
  }

  const signOut = () => {
    auth
      .signOut()
      .then(() => {
        console.log('signed out')
      })
      .catch((err) => {
        console.log('err', err)
      })
  }

  return (
    <div className="flex h-screen w-full items-center justify-center bg-[#111827]">
      <MetadataComponent />
      <main className="flex flex-col items-center justify-center">
        <button
          onClick={signInWithGoogle}
          className="rounded-md border border-white px-4 py-2 text-white"
        >
          Sign in with google
        </button>
      </main>
    </div>
  )
}

export default Login


Dashboard.tsx
import type { NextPage } from 'next'
import { useRouter } from 'next/router'
import { useContext, useEffect } from 'react'
import MetadataComponent from '../components/MetadataComponent'
import { UserContext } from '../lib/context'
import { auth } from '../lib/firebase'

const Dashboard: NextPage = () => {
  const router = useRouter()

  const { user, authLoading } = useContext(UserContext)
  useEffect(() => {
    if (!authLoading && !user) {
      redirectToLogin()
    }
  }, [authLoading, user])
  console.log('user...', user)
  console.log('authLoading...', authLoading)

  const redirectToLogin = () => {
    router.push('/login')
  }
  const signOut = () => {
    auth
      .signOut()
      .then(() => {
        console.log('signed out')
        router.push('/login')
      })
      .catch((err) => {
        console.log('err', err)
      })
  }

  return (
    <div className="flex h-screen w-full items-center justify-center bg-[#111827]">
      <MetadataComponent />
      <main className="flex flex-col items-center justify-center">
        <p className="text-white">Dashboard</p>
        <button
          onClick={signOut}
          className="rounded-md border border-white px-4 py-2 text-white"
        >
          Signout
        </button>
      </main>
    </div>
  )
}

export default Dashboard