Create Protected Routes In NextJS and NextAuth

Create Protected Routes In NextJS and NextAuth

Protecting Routes from unauthenticated users is a crucial part of any app.

In this article, I'll show you exactly how to create protected routes in your NextJS application using NextAuth.

I'll use a JWT token as an example, with the accessToken being saved in the session. Check How to implement NextAuth credentials provider with external API and login page article for more information.

Because of the way Next.js handles getServerSideProps and getInitialProps, every protected page load must make a server-side request to determine whether the session is valid before generating the requested page (SSR). This increases server load, but there is an option if you are comfortable making requests from the client. You can use useSession to ensure that you always have a valid session. If no session is identified after the initial loading state, you can determine the appropriate action to take.

First, let's modify our _app.js file.

import { SessionProvider, useSession, signIn } from 'next-auth/react';
export default function App({
  Component,
  pageProps: { session, ...pageProps },
}) {
  return (
    <SessionProvider session={session}>
      {Component.auth ? (
        <Auth>
          <Component {...pageProps} />
        </Auth>
      ) : (
        <Component {...pageProps} />
      )}
    </SessionProvider>
  )
}

function Auth({ children }) {
  const { data: session, status } = useSession()
  const isUser = !!session?.user
  React.useEffect(() => {
    if (status === "loading") return
    if (!isUser) signIn()
  }, [isUser, status])

  if (isUser) {
    return children
  }

  // Session is being fetched, or no user.
  // If no user, useEffect() will redirect.
  return <div>Loading...</div>
}

Line #23: Do nothing while in the loading state.

Line #24: If the user is not authenticated, force the user to the login page.

Custom Client Session Handling

Because of how Next.js handles getServerSideProps / getInitialProps, every protected page load must make a server-side request to determine whether the session is valid and then build the requested page. This alternate technique enables for the display of a loading status on the initial check, and all subsequent page transitions will be client-side, eliminating the need to check with the server and regenerate pages.

Consider the "/dashboard" page. Only authorized users should be able to view this page.

export default function Dashboard() {
  const { data: session } = useSession()
  return "Some super secret dashboard"
}

Dashboard.auth = true

The session is always non-null inside this page, all the way down the React tree.

Notice that we are using Dashboard.auth = true to check if the user is authenticated, if not redirect to the login page.

We need to add auth = true to every page that we want to protect from the unauthenticated user.

It is simple to extend/modify to support something similar to an options object for role-based authentication on pages. As an example:

Dashboard.auth = {
  role: "admin",
  loading: <LoadingSkeleton />,
  unauthorized: "/login-with-different-user", // redirect to this url
}

I hope this post helps.