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.