DEV Community

Cover image for Building an Authentication Wrapper in React/Next.js + GraphQL 💪
Sohail SJ | chakraframer.com
Sohail SJ | chakraframer.com

Posted on

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

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!

Top comments (8)

Collapse
 
sahilchaubey profile image
sahilchaubey03

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! 🙌

Collapse
 
sahil_reigns_4776e181e6f7 profile image
ReignsEmpire

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

Collapse
 
sahil_chaubey_45db49be580 profile image
WebDevWarrior

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!

Collapse
 
superdevchaurasia profile image
Ashfak

Your example is clear and makes implementing this so much easier

Collapse
 
yongze_chen_7258313d32783 profile image
yongze chen

111

Collapse
 
thesohailjafri profile image
Sohail SJ | chakraframer.com

Hehe Thanks Man for commenting

Collapse
 
devops_devrel profile image
Memories From

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?

Collapse
 
sahil_chaubey_5417dfa7caa profile image
ReactNinja

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