0

I have following bit of code where <AuthContext> provide authentication objects and methods, <ProtectedRoute> component which guards the authentication required routes.

But the problem is when I login and refresh inside authenticated page, user object fetched from the useAuth hook returns null, but if I use next/link it works fine and user object is preserved.

AuthContext.tsx

const AuthContext = createContext<any>({})

export const useAuth = () => useContext(AuthContext)

type Props = {
  children: React.ReactNode
}

const LOGIN = gql`...`

export const AuthContextProvider = ({ children }: Props) => {
  const [user, setUser] = useState<object | null>(null)
  const [loginUser, { data, loading, error }] = useMutation(LOGIN)
  const router = useRouter()

  useEffect(() => {
    // in case of first login set user and token
    // push user to route he belongs
    if (data != null) {
      if (data.authenticate.jwt !== null) {
        console.log('Setting user with JWT decoding...')
        const decoded = jwt_decode(data.authenticate.jwt)
        setUser({
          role: decoded.role,
          id: decoded.id,
        })
        localStorage.setItem('token', data.authenticate.jwt)
      }
    }

    const token = localStorage.getItem('token')
    // if token is present set the user object
    if (token !== null) {
      console.log('Token present')
      const decoded = jwt_decode(token)
      // console.log(user)
      console.log(`Decoded token : ${JSON.stringify(decoded)}`)
      setUser({
        role: decoded.role,
        id: decoded.id,
      })
    } else {
      setUser(null)
    }
  }, [data])

  const login = async (username: string, password: string) => {
    loginUser({
      variables: {
        username,
        password,
      },
    })
  }

  const logOut = async () => {
    console.log('Logging out ...')
    setUser(null)
    data = null
    localStorage.removeItem('token')
  }

  // if (error) return <p>Submission error! ${error.message}</p>

  return (
    <AuthContext.Provider value={{ user, login, logOut }}>
      {error || loading ? null : children}
    </AuthContext.Provider>
  )
}

ProtectedRoute.tsx

const PUBLIC_PATHS = ['/']
const ADMIN_ROUTES = [...]
const SUPERVISOR_ROUTES = [...]


export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user } = useAuth()
  const router = useRouter()
  const [authorized, setAuthorized] = useState(false)

  useEffect(() => {
    console.log(`Current User : ${JSON.stringify(user)}`)

    const isAdminRoute = ADMIN_ROUTES.includes(router.pathname)
    const isSupervisorRoute = SUPERVISOR_ROUTES.includes(router.pathname)

    // if an token is present send user to
    // authorized route
    if (user !== null) {
      // @ts-ignore
      if (user.role === 1 && isAdminRoute) {
        setAuthorized(true)
        console.log(`Pushing to ${router.pathname}`)
        router.push(router.pathname)
        // @ts-ignore
      } else if (user.role === 2 && isSupervisorRoute) {
        setAuthorized(true)
        console.log(`Pushing to ${router.pathname}`)
        router.push(router.pathname)
      } else {
        console.log(`Invalid role! user: ${JSON.stringify(user)}`)
      }
    } else {
      setAuthorized(false)
      console.log('Sending you to login page')
      // "/" have the login page
      router.push('/')
    }
  }, [user]) // router is avoided from deps to avoid infinite loop

  return <>{authorized && children}</>
}

_app.tsx

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}

type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}

const noAuthRequired = ['/']

function App({ Component, pageProps }: AppPropsWithLayout) {
  const getLayout = Component.getLayout ?? ((page) => page)

  const router = useRouter()

  return (
    <ApolloProvider client={CLIENT}>
      <AuthContextProvider>
        {getLayout(
          noAuthRequired.includes(router.pathname) ? (
            <Component {...pageProps} />
          ) : (
            <ProtectedRoute>
              <Component {...pageProps} />
            </ProtectedRoute>
          ),
        )}
      </AuthContextProvider>
    </ApolloProvider>
  )
}

export default App

Current possible workaround is to read JWT in the ProtectedRoute to get user information.

I there anyway to preserve user object on page refresh?

user158
  • 12,852
  • 7
  • 62
  • 94

0 Answers0