Firebase does not authenticate when using next auth

I'm using next-auth with firebase adapter. Everything is working fine in terms of saving users in database, but something is not working in terms of authentication.

import NextAuth from "next-auth"

import GoogleProvider from "next-auth/providers/google"
import { FirebaseAdapter } from "@next-auth/firebase-adapter"

import { db } from "../../../utils/firebase/firebase"
import * as firestoreFunctions from 'firebase/firestore'

import { adminAuth } from "../../../utils/firebase/firebaseAdmin"

import { getAuth, signInWithCustomToken } from "firebase/auth"

const auth = getAuth()

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
      state: false,
    }),
  ],
  adapter: FirebaseAdapter({
    db: db,
    ...firestoreFunctions,
  }),
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      console.log(user, 'user')
      const customToken = await adminAuth.createCustomToken(user.id)
      const customSignIn = await signInWithCustomToken(auth, customToken)
      console.log(customSignIn, 'customSignIn')
      console.log(customSignIn.user, 'customSignIn.user')
      user = customSignIn.user
      console.log(user, 'user 2')
      return true
    },
    async redirect({ url, baseUrl }) {
      return baseUrl
    },
    async session({ session, user, token }) {
      if (session?.user) {
        session.user.id = token.sub
      }
      return session
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      if (isNewUser) {
        const additionalClaims = {
          isStudent: true,
          isTeacher: false,
          isStaff: false,
          isAdmin: false
        }
        const customToken = await adminAuth.createCustomToken(token.sub, additionalClaims)
        const customSignIn = await signInWithCustomToken(auth, customToken)
        user = customSignIn.user

      }
      return token
    }

  },

  session: {
    strategy: 'jwt',
  },
})

My users can log in, but are not authenticated.

const auth = getAuth()

onAuthStateChanged(auth, (user) => {
  if (user) {
    // User is signed in, see docs for a list of available properties
    // https://firebase.google.com/docs/reference/js/firebase.User
    console.log('user')
    console.log(user)
    // ...
  } else {
    console.log('no user')
    // User is signed out
    // ...
  }
})

The Observer returns 'no user', but I'm logged in.

Upvotes: 6

Views: 5945

Answers (2)

Ok, so what I finally did is: as @esi're said, pass the customToken created with firebase auth, to the session:

async jwt({
  token,
  user,
  account,
  profile,
  isNewUser
}) {
  if (isNewUser || user) {
    const additionalClaims = {
      isStudent: true,
      isTeacher: false,
      isStaff: false,
      isAdmin: false
    }
    const customToken = await adminAuth.createCustomToken(token.sub, additionalClaims)
    console.log(customToken, '***customToken')
    token.customToken = customToken
  }
  return token
}

Then, in the session callback:

async session({
  session,
  token,
  user
}) {
  if (session ? .user) {
    session.user.id = token.sub
    session.customToken = token.customToken
  }
  return session
},

And finally, in the page where users are first redirected:

  const [user, setUser] = useState()

  useEffect(() => {

    if (status === "authenticated") {
      const loggedInUser = {
        userName: session.user.name,
        userEmail: session.user.email,
        userPhoto: session.user.image,
        userUID: session.user.id,
      }

      signInWithCustomToken(auth, session.customToken)


      dispatch(setActiveUser(loggedInUser))

    }
  }, [status])

  const handleLogOut = () => {
    dispatch(setUserLogOutState())
    logout()
    signOut({
      callbackUrl: '/'
    }) //TODO: goodbye page
  
  
  // Rest of jsx code when session is true

And that's it. Hope it works for someone else too.

Thanks !

Upvotes: 3

Esi're
Esi're

Reputation: 66

You're not seeing a logged in user because the firebase auth is being done on the server side, and not the client side.

You should try passing the customToken to the session, then use it on the client side to sign in the user to firebase auth.

You would also want to wrap the useSession hook in a custom hook like below and use that instead of useSession.

const useFirebaseSession = () => {
  const session = useSession();
  const [status, setStatus] = useState(session.status);

  useEffect(() => {
    if (session && session.status === 'authenticated') {
      signInWithCustomToken(auth, session.customToken).then(() => {
        setStatus('authenticated');
      });
    }
  }, [session]);

  useEffect(() => {
    if(session.status !== 'authenticated') {
      setStatus(session.status)
    }
  }, [session.status]);

  return { data: session.data, status };
}

Upvotes: 5

Related Questions