Building an Authentication Wrapper in React/Next.js + GraphQL 💪
Sohail SJ | TheZenLabs

Sohail SJ | TheZenLabs @thesohailjafri

About: Developer | Biker | Athlete | Youtuber 70k Views on Dev.to

Location:
Mumbai | India
Joined:
Apr 5, 2023

Building an Authentication Wrapper in React/Next.js + GraphQL 💪

Publish Date: Nov 14 '24
50 8

Managing authentication in a React/Next.js application with GraphQL can sometimes be tricky, especially when you need to protect routes and manage user sessions efficiently. In this article, we'll walk through setting up an AuthWrapper component to handle authentication seamlessly in your application.

Prerequisites

Before diving in, ensure you have the following:

  • A Next.js project set up.
  • A backend or API with GraphQL support for authentication.
  • Basic understanding of React hooks like useEffect and Apollo Client or any other GraphQL client.

The Goal

We want to create a reusable AuthWrapper component that:

  • Protects routes by redirecting unauthenticated users.
  • Fetches authenticated user data (e.g., customer details) after login.
  • Shows a loader during the authentication process.

Setting Up the AuthWrapper Component

Here’s the updated implementation of the AuthWrapper component:

import { useEffect } from 'react'
import { useRouter } from 'next/router'
import { useQuery } from '@apollo/client'
import { useAuth } from '@/hooks/useAuth'
import Loader from '@/components/Loader'
import { GET_USER_ME_QY } from '@/lib/graphql'
import { UserDocument } from '@/types'

const authTokenName = 'authToken'

interface Props {
  children: React.ReactNode
}

const AuthWrapper = ({ children }: Props) => {
  const router = useRouter()
  const { setIsAuth, setUser, isAuthing, setIsAuthing } = useAuth()

  // Redirect to login if no auth token exists
  useEffect(() => {
    if (typeof window === 'undefined') return
    if (localStorage.getItem(authTokenName) === null) {
      router.push('/login')
    }
  }, [router])

  // Query to fetch authenticated user data
  const { refetch, loading } = useQuery<{
    userMe: UserDocument
  }>(GET_USER_ME_QY, {
    skip:
      typeof window === 'undefined' ||
      localStorage.getItem(authTokenName) === null,
    fetchPolicy: 'network-only',
    onError: (error) => {
      console.error('Error fetching user me', error)
      setIsAuth(false)
      setIsAuthing(false)
    },
    onCompleted: (data) => {
      setIsAuth(true)
      setIsAuthing(false)
      setUser(data.userMe)
    },
  })

  // Trigger refetch if authentication token exists
  useEffect(() => {
    if (
      typeof window !== 'undefined' &&
      localStorage.getItem(authTokenName) !== null
    ) {
      refetch()
    }
  }, [refetch])

  return isAuthing ? <Loader /> : <>{children}</>
}

export default AuthWrapper
Enter fullscreen mode Exit fullscreen mode

Key Explanations

1. Authentication Check

This is crucial for protecting your routes. If a user tries to access a protected page without being authenticated, they will be seamlessly redirected to the login page, enhancing the user experience.

useEffect(() => {
  if (localStorage.getItem(authTokenName) === null) {
    router.push('/login')
  }
}, [router])
Enter fullscreen mode Exit fullscreen mode

2. Fetching Authenticated User Data

The useQuery hook fetches the authenticated user data using the GET_USER_ME_QY query:

const { refetch, loading } = useQuery<{
  userMe: UserDocument
}>(GET_USER_ME_QY, {
  skip:
    typeof window === 'undefined' ||
    localStorage.getItem(authTokenName) === null,
  fetchPolicy: 'network-only',
  onError: (error) => {
    console.error('Error fetching user me', error)
    setIsAuth(false)
    setIsAuthing(false)
  },
  onCompleted: (data) => {
    setIsAuth(true)
    setIsAuthing(false)
    setUser(data.userMe)
  },
})
Enter fullscreen mode Exit fullscreen mode

Note here Apollo Client provide handy callback functions like onError and onCompleted to handle errors and data respectively. But you can use your own error and success handling logic.

3. Loader for Authentication Process

Loader provides visual feedback to users, indicating that their authentication status is being verified, which is essential for a smooth user experience.

return isAuthing ? <Loader /> : <>{children}</>
Enter fullscreen mode Exit fullscreen mode

4. State Management

We use a custom useAuth hook to manage authentication state across the app. Below is the implementation of the useAuth hook:

import { useState, useContext, createContext } from 'react'
import { UserDocument } from '@/types'

const AuthContext = createContext(null)

export const AuthProvider = ({ children }) => {
  const [isAuthing, setIsAuthing] = useState(true)
  const [isAuth, setIsAuth] = useState(false)
  const [user, setUser] = useState<UserDocument | null>(null)

  return (
    <AuthContext.Provider
      value={{
        isAuthing,
        setIsAuthing,
        isAuth,
        setIsAuth,
        user,
        setUser,
      }}
    >
      {children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => useContext(AuthContext)
Enter fullscreen mode Exit fullscreen mode

Integrating AuthWrapper in Your App

To use the AuthWrapper, wrap your app or specific pages that require authentication:

import AuthWrapper from '../components/AuthWrapper'

const ProtectedPage = () => {
  return (
    <AuthWrapper>
      <h1>You must be logged in to see me user!</h1>
    </AuthWrapper>
  )
}

export default ProtectedPage
Enter fullscreen mode Exit fullscreen mode

Conclusion

Building an authentication wrapper in React/Next.js with GraphQL can help streamline your app's authentication process. By following the steps outlined in this article, you can create a reusable AuthWrapper component that handles authentication, protects routes, and fetches user data efficiently.

Get In Touch

Feel free to share your thoughts or ask for further clarification by reaching out to me. Happy coding! Hack on!

Comments 8 total

  • yongze chen
    yongze chenNov 18, 2024

    111

  • sahilchaubey03
    sahilchaubey03Nov 18, 2024

    This is an incredibly detailed and well-explained guide! The use of useQuery for fetching user data with Apollo Client is particularly elegant. Thanks for sharing! 🙌

  • Memories From
    Memories FromNov 18, 2024

    This is amazing, but what happens if the token expires while the user is still on a protected page? Does it revalidate or force a logout?

  • WebDevWarrior
    WebDevWarriorNov 18, 2024

    Can you elaborate on how error handling is managed for edge cases, like a network failure or malformed token? Would love to see some fallback mechanisms!

  • ReignsEmpire
    ReignsEmpireNov 18, 2024

    This looks super clean and reusable! The idea of combining GraphQL with React hooks for auth management is brilliant. Great job! 💡

  • ReactNinja
    ReactNinjaNov 18, 2024

    Love how you've incorporated Loader for a better user experience during the authentication process

  • Ashfak
    AshfakNov 18, 2024

    Your example is clear and makes implementing this so much easier

Add comment