Atlante Avila
Atlante Avila

Reputation: 1488

How to persist authentication in Next.js using firebase after token expiration and or refreshing the page

I'm building an app with firebase and next.js. I'm having a hard time with firebase's authentication flows. Everything was coming along well until I noticed a bug/error. If I leave my computer logged in to the app for a while and whenever I refresh the page, It seems like both cause me to be redirected back to my login page. I can see why a refresh would trigger my redirect, as there may not be enough time for the onAuthStateChange to be checked in time for const { user } = useAuth() to run so since there is no user at initial page load (after refresh.) It will go in to the { else } causing me to redirect at this point. But the funny thing is that if I simply click my dashboard (a protected page) link, I'm still authenticated. No redirections. Below is my code for my auth component:

AuthComp.js:

import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useAuth } from "../../context/AuthContext";
function LoadingScreen() {
    return <div className="fixed top-0 right-0 h-screen w-screen z-50 flex justify-center items-center">
        <div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>
    </div>
}
export function AuthComp(props) {
    const [isAuthenticated, setIsAuthenticated] = useState(false)
    const router = useRouter();
    const { user } = useAuth();
    useEffect(() => {
        let out;
        if (user) {
            setIsAuthenticated(true)
            return () => out ? out() : null
        } else {
            router.push('/auth')
        }
        return () => out;
    }, [user])
    if (!user && (!isAuthenticated)) {
        return <LoadingScreen />;
    }
    return <props.Component user={user} />
};

Here is my code for my auth context file: AuthContext.js:

import React, { useState, useEffect, createContext, useContext } from 'react'
import { fbase } from '../utils/auth/firebaseClient'

export const AuthContext = createContext()

export default function AuthProvider({ children }) {
    const [user, setUser] = useState(null)
    const [loadingUser, setLoadingUser] = useState(true) // Helpful, to update the UI accordingly.

    useEffect(() => {
        // Listen authenticated user
        const unsubscriber = fbase.auth.onAuthStateChanged(async (user) => {
            try {
                if (user) {
                    setUser(user);
                } else {
                    setUser(null)
                    return;

                }
            } catch (error) {
                // Most probably a connection error. Handle appropriately.
                console.log('an error occurred', error);
            } finally {
                setLoadingUser(false)
            }
        })

        // Unsubscribe auth listener on unmount
        return () => unsubscriber()
    }, [])

    return (
        <AuthContext.Provider value={{ user, setUser, loadingUser }}>
            {children}
        </AuthContext.Provider>
    )
}

// Custom hook that shorthands the context!
export const useAuth = () => useContext(AuthContext)

Upvotes: 1

Views: 3085

Answers (1)

Doug Stevenson
Doug Stevenson

Reputation: 317828

there may not be enough time for the onAuthStateChange to be checked in time

The first user result from onAuthStateChanged is definitely not guaranteed to happen immediately. You should expect that the first callback will take some time, as the user's persisted token is first loaded then verified.

Before the callback triggers the first time, you should assume an "unknown" state for the user. The user is neither signed in nor signed out until that first callback. I suggest writing your app with this trinary state in mind. (Related, firebase.auth().currentUser will always be null when a page first loads.) To read more about this behavior, I suggest reading this blog post.

Upvotes: 6

Related Questions