Editor’s note: This guide was last updated by Nefe Emadamerho-Atori on 22 March 2024 to incorporate information about creating protected routes with NextAuth.js and using NextAuth Middleware for authentication checks, as well as checking the user login state using NextAuth hooks like useSession
and getServerSession
.
Authentication is an important and sensitive feature in applications where a user’s credentials, such as username, email, and password, are used to verify their identity.
In this article, we’ll set up client-side authentication that doesn’t require a password in Next.js using a powerful and secure library called NextAuth.js. Our app will allow users to log in using their GitHub, Google, and Facebook accounts. Upon successful signup, we will display the user’s profile picture and email, which we’ll retrieve from their social media accounts.
To build our app, we will use React Hooks and functional components.
NextAuth.js is a completely secured and flexible authentication library designed to sync with any OAuth service, with full support for passwordless sign-in.
NextAuth.js can be used with or without a database, and it has default support for popular databases such as MySQL, MongoDB, PostgreSQL, and MariaDB. It can also be used without a database by syncing with services like OAuth and JSON Web Token.
With a library like NextAuth.js, you don’t need to be an expert in identity protocol like you would if you were to use OAuth to build secured Next.js applications. NextAuth.js is built to avoid the need to store sensitive data, such as a user’s password. It works perfectly with Next.js. With just 20 lines of code, you can implement an authentication feature in your app.
NextAuth.js has a client-side API you can use to interact with sessions in your app. The session data returned from the Providers contains user payload and this can be displayed to the user upon successful login.
The session data returned to the client looks like this:
{ expires: '2055-12-07T09:56:01.450Z'; user: { email: '[email protected]'; image: 'https://avatars2.githubusercontent.com/u/45228014?v=4'; name: 'Ejiro Asiuwhu'; } }
The payload doesn’t contain any sensitive data. The session payload or data is meant for presentation purposes — that is, it’s meant to be displayed to the user.
NextAuth.js also provides the useSession
React Hook, which can be used to check user login status. Meanwhile, NextAuth.js provides a REST API that is used by the React app. To learn more about what the REST API NextAuth exposes, check out the official docs.
To get started, create a new Next.js application by running the command below:
npx create-next-app@latest
You will be prompted to go through a series of questions; your selections should be the same as the ones from the image below:
Now, change the directory into the project folder and launch the development server:
npm run dev # or yarn run dev
By default, the project will run on port 3000
. Launch your browser and navigate to http://localhost:3000
. You should end up with this:
Now that we have the Next.js starter application set up, we’re ready to learn how to authenticate a Next.js app with NextAuth.js.
This NextAuth.js client-side authentication tutorial will cover the following:
next-auth is an npm package, so installing it is easy:
npm install next-auth # or yarn add next-auth
In our demo, we’ll give users the choice to log in to our app using their GitHub, Google, or Facebook accounts.
To add a GitHub Authentication Provider, which essentially allows users to log in to our app using their GitHub account, we need to first create a GitHub OAuth app. Click on New OAuth app and fill out the form accordingly with the following details:
http://localhost:3000
/api/auth/callback
, resulting in http://localhost:3000/api/auth/callback
After registering our OAuth app, GitHub creates a Client ID and Client Secret specifically for our newly created app. Copy the Client ID and secret key to your clipboard. Click on Generate new client secret to get a Client Secret.
NEXTAUTH_SECRET
environmentNEXTAUTH_SECRET
is a critical environment variable used by NextAuth.js to sign and verify JWTs (JSON Web Tokens) and encrypt session data. This value should ideally be a long, random string that is kept secure and never exposed publicly.
You can use the Node.js built-in crypto module or an OpenSSL command to generate a secret:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" or openssl rand -base64 32
This generates a secure, random, 32-byte value encoded as a hexadecimal string. Set this string as the value of the NEXTAUTH_SECRET
environment variable in your .env.local
file with your GitHub client and secret keys:
GITHUB_ID=<GITHUB_CLIENT_ID> GITHUB_SECRET=<GITHUB_CLIENT_SECRET> NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=<NEXTAUTH_SECRET_KEY>
Replace <GITHUB_CLIENT_ID>
, <GITHUB_CLIENT_SECRET>
, and <NEXTAUTH_SECRET_KEY>
with your GitHub client, secret key, and your NextAuth secret.
Note: If you don’t provide a NEXTAUTH_SECRET
variable, NextAuth.js will automatically generate one for you when the server starts. However, this dynamically generated secret won’t persist across server restarts. This means that all your signed tokens and sessions will become invalid every time your server restarts, as they will fail the verification against the new secret. Therefore, it is strongly recommended that you provide your own NEXTAUTH_SECRET
variable for consistent session and token management in production environments.
Now, back to our app. Create an app/api/auth/[…nextauth]/route.tsx
file in the /app
folder and add the following code:
import NextAuth from "next-auth"; import type { NextAuthOptions } from "next-auth"; import GitHubProvider from "next-auth/providers/github"; export const authOptions: NextAuthOptions = { providers: [ GitHubProvider({ clientId: process.env.GITHUB_ID as string, clientSecret: process.env.GITHUB_SECRET as string, }), ], secret: process.env.NEXTAUTH_SECRET, } const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
In the above code snippet, GitHubProvider
is imported from "next-auth/providers/github"
and is included in the providers
array of the authOptions
configuration object, which is of type NextAuthOptions
. This object is passed to the NextAuth
function, creating a handler for processing authentication requests.
The client ID and secret for GitHub are sourced from the environment variables GITHUB_ID
and GITHUB_SECRET
. Lastly, the handler is exported under the aliases GET
and POST
, indicating it will manage both types of HTTP requests for authentication routes.
Then, navigate to http://localhost:3000/api/auth/signin
, and you should see this:
NextAuth.js provides two hooks to check if a user is currently logged in:
useSession
: A client-side method for fetching session datagetServerSession
: A server-side method for fetching session dataSessionProvider
Before we can fetch the session data, we first need to give our application access to that data. To achieve this, NextAuth.js provides a SessionProvider
component we can configure and wrap around our application.
Note: only the useSession
hook needs the SessionProvider
to access a session’s data. However, we will configure the SessionProvider
first before exploring the two methods for checking the user login state and obtaining session information.
Let’s start by updating the layout.tsx
file with the code snippet below:
import { SessionProvider } from 'next-auth/react'; import { getServerSession } from "next-auth"; import '../styles/globals.css'; export default async function RootLayout({ children }) { const session = getServerSession(); return ( <html lang="en"> <body> <SessionProvider session={session}> {children} </SessionProvider> </body> </html> ); }
In the above code snippet, we imported the SessionProvider
component from the next-auth/react
package and getServerSession
from next-auth
. We will use them to provide session state to all components in your application, allowing you to access a session’s data.
If you’ve followed the steps so far, you’ll notice something strange: an error occurs. This is because:
SessionProvider
component is a client component that uses the Context API. That’s why it’s exported from next-auth/react
and not next-auth
We can solve this error by flagging the SessionProvider
as a Client Component and then exporting it. Create a SessionProvider.tsx
file and input the code below:
"use client"; //tells Next.js to render this component on the client import { SessionProvider } from "next-auth/react"; export default SessionProvider;
Now, instead of importing SessionProvider
directly from NextAuth.js, we will use the client-compatible version we just created. Here’s the updated code for the layout.tsx
file:
import SessionProvider from "../SessionProvider"; //next SessionProvider imported import { getServerSession } from "next-auth"; import '../styles/globals.css'; export default async function RootLayout({ children }) { const session = getServerSession(); return (...); //previous code here remains unchanged }
useSession
HookHaving configured the SessionProvider
, let’s use the useSession
Hook to fetch the logged-in user’s info and display that data in the application.
Create a ClientUserSession.tsx
file with the following code:
"use client"; //tells Next.js to render this component on the client import Link from "next/link"; import { useSession, signIn, signOut } from "next-auth/react"; export default function Home() { const { data: session } = useSession(); return ( <main> <div className="header"> <Link href="/"> <p className="logo">NextAuth.js</p> </Link> {session && ( <Link href="#" onClick={() => signOut()} className="btn-signin"> Sign out </Link> )} {!session && ( <Link href="#" onClick={() => signIn()} className="btn-signin"> Sign in </Link> )} </div> </main> ); }
In the above code snippet, we imported the next-auth hooks signIn
, signOut
, and useSession
:
signIn
Hook redirects you to the GitHub login pagesignOut
Hook destroys the activate session and logs the user outuseSession
Hook provides us with the details of the logged-in userIn summary, we used useSession
to check if a session exists and conditionally render the sign-in and sign-out links. Note that we also use the “use client” directive here, or else the same error will occur, as shown in the image below:
getServerSession
HookLet’s see how to check the login state with getServerSession
:
import { getServerSession } from "next-auth"; import styles from "./page.module.css"; export default async function Home() { const session = await getServerSession(authOptions); return ( <main> <div style={{ display: "flex", justifyContent: "center", alignItems: "center", }} > <div className="header"> {session && ( <> <span>Session data from server: </span> {session?.user?.name} <br /> </> )} {!session && ( <> Not signed in! <br /> </> )} </div> </div> </main> ); }
Here, we fetched the current session
object from getServerSession
‘s return value and conditionally rendered that data. We don’t need to use the “use client” directive for getServerSession
because it runs on the server.
Note that the code snippet doesn’t contain any links or click handlers like we did with useSession
. This is because we can’t use those operations on the server, as they are client-only methods. Therefore, we should only use getServerSession
when we want to fetch data.
useSession
and getServerSession
useSession
works on the client, while getServerSession
works on the serveruseSession
, but not with getServerSession
useSession
requires the SessionProvider
context to work, while getServerSession
doesn’tgetServerSession
provides a better user experience than useSession
. The GIF below shows what happens when you reload the page. The session data obtained from useSession
takes a moment to render, while getServerSession
‘s session data appears instantly. This means that getServerSession
makes the UI feel more responsive, leading to a better UX:Highlighting the differences between useSession
and getServerSession
will help you know the scenarios that both methods are best suited for.
So far in your code, you’ve been using the custom login page provided by NextAuth.js. Now, let’s create a custom login page for your application. To do that, you need to modify the app/api/auth/[…nextauth]/route.tsx
file with NextAuth’s pages
option:
... export const authOptions: NextAuthOptions = { providers: [ ... ], secret: process.env.NEXTAUTH_SECRET, pages: { signIn: "/signin", }, } ...
Here, you overrode the signIn
value of the default login page and set it to /signin
. Going forward, this is the page NextAuth.js will use for user authentication. It will redirect users to this page when authentication is required.
Now, you need to make the page. Create a signin/route.js
file in the app
directory and add the code snippet below:
import { getServerSession } from "next-auth"; import CustomSignInButton from "../components/CustomSignInButton"; import { authOptions } from "../api/auth/[...nextauth]/route"; export default async function CustomLoginPage() { const session = await getServerSession(authOptions); return ( <div> <h2> A custom NextAuth login page</h2> {session && <p>You are already signed in.</p>} {!session && ( <div> <CustomSignInButton /> </div> )} </div> ); }
Here’s the code for the CustomSignInButton
component:
"use client"; import { signIn } from "next-auth/react"; export default function CustomSignInButton() { return ( <button onClick={() => signIn()}> <svg>...</svg>{" "} Log in with GitHub </button> ); }
With that, anyone who tries to log in will be redirected to the custom login page below:
NextAuth provides three methods for protecting routes:
Let’s explore these in detail.
Whether client-side or otherwise, setting up protected routes in NextAuth.js is relatively straightforward. First, create the route that needs protecting.
Create a protected/page.js
file in the /app
directory and update it with the code below:
import { redirect } from "next/navigation"; import { useSession } from "next-auth/react"; export default async function ClientProtectedRoute() { const { data: session } = useSession(); if (!session || !session.user) { redirect("api/auth/signin"); } return ( <h1> This is a client-side protected route and only logged-in users can access it </h1> ); }
The code above checks if there isn’t a session. That is if there’s no logged-in user. If there isn’t, it uses Next.js’ redirect
function to redirect the user to the sign-in page. If there is a session, the user gets access to the page. This ensures that only logged-in users can access the /protected
route.
The only difference between the client-side and server-side methods is that the client-side utilizes useSession
, while the server-side utilizes getServerSession
. Here’s the code for the server-side approach:
import { redirect } from "next/navigation"; import { getServerSession } from "next-auth"; export default async function ServerProtectedRoute() { const session = await getServerSession(); if (!session || !session.user) { redirect("api/auth/signin"); } return ( <h1> This is a server-side protected route and only logged-in users can access it </h1> ); }
Similar to the client-side approach, the code above redirects to the sign-in page if there isn’t a session and allows access to the /protected
route if there is.
NextAuth Middleware allows you to run certain code before any page (even static ones) is loaded, thus improving both the speed and safety of your website.
One of the main roles of NextAuth’s middleware is managing authentication; it verifies users and controls their access to specific parts of your site by checking if a user is authenticated/authorized. If they aren’t, they will be redirected to the sign-in page.
To secure pages with the middleware, create a middleware.ts
file at the root of your application or in the src
directory, and update it with the code below:
export { default } from "next-auth/middleware"; export const config = { matcher: [ "/protected", "/protected/:path*", //use this to protect all child routes of '/protected' ], };
The code exports two things from middleware.js
:
default
object from next-auth/middleware
config
object where we define the routes we want to protect. The matcher
property dictates the paths — and subpaths — the middleware function should be applied toThe middleware code works similarly to the client and server-side methods. The only difference is that we don’t need to explicitly define the redirect route. It automatically redirects non-logged-in users to the sign-in page.
Callbacks in NextAuth.js are asynchronous functions that you can use to control what happens when an action is performed, such as user sign-in, redirection, session creation, or JWT generation.
These callbacks allow you to implement access controls, integrate with external databases or APIs, and customize the behavior of the authentication flow.
NextAuth.js provides several callbacks that you can configure in your application:
The signIn
callback is used to control whether a user is allowed to sign in or not. It receives parameters such as user
, account
, profile
, email
, and credentials
, depending on the authentication provider used. You can use this callback to implement custom logic for allowing or denying user sign-in based on specific conditions.
Here’s an example of the signIn
callback:
callbacks: { async signIn({ user, account, profile, email, credentials }) { const isAllowedToSignIn = true if (isAllowedToSignIn) { return true } else { // Return false to display a default error message return false // Or you can return a URL to redirect to: // return '/unauthorized' } } }
The redirect
callback is called anytime the user is redirected to a callback URL (e.g., on sign-in or sign-out). By default, NextAuth.js only allows URLs on the same origin as the site. You can use this callback to customize this behavior and allow or deny specific redirect URLs.
Here’s the default implementation of the redirect
callback:
callbacks: { async redirect({ url, baseUrl }) { // Allows relative callback URLs if (url.startsWith("/")) return `${baseUrl}${url}` // Allows callback URLs on the same origin else if (new URL(url).origin === baseUrl) return url return baseUrl } }
The jwt
callback is called whenever a JSON Web Token is created (e.g., at sign-in) or updated (e.g., whenever a session is accessed on the client). This callback allows you to customize the contents of the JWT by adding or modifying claims.
Here’s an example of the jwt
callback that persists an OAuth access token and user ID in the JWT:
callbacks: { async jwt({ token, account, profile }) { // Persist the OAuth access_token and user id to the token right after sign-in if (account) { token.accessToken = account.access_token token.id = profile.id } return token } }
The session
callback is called whenever a session is checked, such as when using getSession
, useSession
, or /api/auth/session
. By default, NextAuth.js only returns a subset of the token for security reasons. If you want to make something available to the client that you added to the token (like access_token
and user.id
from the jwt
callback), you need to explicitly forward it in this callback.
Here’s an example of the session
callback that sends the access token and user ID to the client:
callbacks: { async session({ session, token }) { // Send properties to the client, like an access_token and user id from a provider. session.accessToken = token.accessToken session.user.id = token.id return session } }
In this post, we implemented user authentication in Next.js using NextAuth.js, which is a secured library to identify our users, get user profile information, and render them in our frontend. We covered most of the use cases, but there is a lot more you can do with NextAuth.js, such as adding a database using JSON Web Token, securing pages, and more.
Let me know in the comment section below what you thought of this tutorial. You can also reach me on Twitter and GitHub. Thank you for reading!
Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your Next.js apps — start monitoring for free.
The AHA stack — Astro, htmx, and Alpine — is a solid web development stack for smaller apps that emphasize frontend speed and SEO.
Explore what Hattip is, how it works, its benefits and key features, and the differences between Hattip and Express.js.
React Shepherd stands out as a site tour library due to its elegant UI and out-of-the-box, easy-to-use React Context implementation.
Cookies are crucial to web development. This article will explore how to handle cookies in your Next.js applications.
13 Replies to "How to use NextAuth.js for client-side authentication in Next.js"
Thanks for the write up
Great article! However, you called it ‘Nuxt-Auth.js’ and ‘Next-Auth.js’ a few times, it is ‘NextAuth.js’
– core team member
Thanks for pointing that out, I’ve fixed the instance of `Nuxt-Auth.js`
Does next-auth work with only SSG?
It has full support for SSR and SSG
Really great article 😊 I really enjoyed reading it and it makes me want to switch to Next.js. I was wondering why you called this client-side rather than severless? Is the authentication not done server side by SSR? I really apologize if I’m wrong, I’m just trying to understand the authentication flow.
Just a heads up that _ap.js should actually be _app.js. Thanks for the post!
👍
website: next-auth.js
Example code : next-auth-example
npm package : next-auth
But it is called NextAuth.js and not next-auth.js as one could guess from all your urls.
Am I missing something here ?
I’m wondering the same. Needing a secret is a red flag that this is running in the backend.
How is it supposed to run in frontend?
Unhandled Runtime Error
Error: React Context is unavailable in Server Components
Call Stack
SessionProvider
can you please resolve this error
Use the “use client” flag at the top of the file.
That error is showing up because “SessionProvider” is using react context which is a client component and for it to work, you must mark the file as a client component with the flag above.
You are using react 13.+.+ I believe