nextjs logo
typescript logo

Next.js Authentication By Example: Using App Router

Published on October 31, 2023
Photo of Dan Arias
Dan AriasStaff Developer Advocate

The focus of this Next.js tutorial is to help developers learn how to secure a Next.js web application by implementing user authentication following the new Next.js App Router paradigm. These examples in this tutorial help you learn the following security concepts:

  • How to add user login, sign-up, and logout to Next.js web applications.
  • How to get user profile information to personalize a Next.js user interface.
  • How to use the new Next.js App Router features to protect application routes.
  • How to make API calls in Next.js to request data from an external protected API.

This Next.js tutorial uses the Auth0 Next.js SDK, which provides developers with a high-level API to handle many user authentication and authorization implementation details. You can now secure your Next.js applications following security best practices while writing less code. The tutorial also uses TypeScript to align with the stack the new Next.js Learn Course uses to build robust applications that reflect the modern web landscape.

Quick Next.js Setup

With the help of Auth0 by Okta, you don't need to be an expert on identity protocols, such as OAuth 2.0 or OpenID Connect, to understand how to secure your web application stack.

You first integrate your Next.js application with Auth0. Your application will then redirect users to an Auth0 customizable login page when they need to log in. Once your users log in successfully, Auth0 redirects them to your Next.js app, creating a session with their authentication and user information.

Get the Next.js Starter Application

We have created a starter project using create-next-app to help you learn Next.js security concepts through hands-on practice. You can focus on building Next.js components and TypeScript services to secure your application.

Start by cloning the web-app_nextjs_typescript_hello-world repository on its starter branch:

COMMAND
git clone -b starter [email protected]:auth0-developer-hub/web-app_nextjs_typescript_hello-world.git

Once you clone the repo, make web-app_nextjs_typescript_hello-world your current directory:

COMMAND
cd web-app_nextjs_typescript_hello-world

Install the Next.js project dependencies as follows:

COMMAND
npm install

This starter Next.js project offers a functional application that consumes localized data to hydrate the user interface. Later on, you'll integrate this Next.js application with a real API server using a backend technology of your choice to get the data.

Open another terminal tab and execute this command to run your Next.js application:

COMMAND
npm run dev

You are ready to implement user authentication in this Next.js App Router project. First, you'll need to configure the Next.js application to connect successfully to Auth0. Afterward, you'll use the Auth0 Next.js SDK to log in users, protect routes, display user profile information, and request protected data from an external API server to hydrate some of the application pages.

Configure Next.js with Auth0

Follow these steps to get started with the Auth0 Identity Platform quickly:

Sign up and create an Auth0 Application

A free account also offers you:

During the sign-up process, you create something called an Auth0 Tenant, representing the product or service to which you are adding authentication.

Once you sign in, Auth0 takes you to the Dashboard. In the left sidebar menu, click on "Applications".

Then, click the "Create Application" button. A modal opens up with a form to provide a name for the application and choose its type. Use the following values:

Name
Auth0 Next.js Code Sample
Application Type
Regular Web Applications
Regular Web Applications

Click the "Create" button to complete the process. Your Auth0 application page loads up.

In the next step, you'll learn how to help Next.js and Auth0 communicate.

What's the relationship between Auth0 Tenants and Auth0 Applications?

Suppose you have a photo-sharing Next.js web app called "Nextigram". You then would create an Auth0 tenant called nextigram. From a customer perspective, Nextigram is that customer's product or service.

Now, say that Nextigram is available on three platforms: web as a single-page application and Android and iOS as a native mobile application. If each platform needs authentication, you need to create three Auth0 applications to provide the product with everything it needs to authenticate users through that platform.

Nextigram users belong to the Auth0 Nextigram tenant, which shares them across its Auth0 applications.

Create a communication bridge between Next.js and Auth0

When using the Auth0 Identity Platform, you don't have to build login forms. Auth0 offers a Universal Login Page to reduce the overhead of adding and managing authentication.

How does Universal Login work?

Your Next.js application will redirect users to Auth0 whenever they trigger an authentication request. Auth0 will present them with a login page. Once they log in, Auth0 will redirect them back to your Next.js application. For that redirecting to happen securely, you must specify in your Auth0 Application Settings the URLs to which Auth0 can redirect users once it authenticates them.

As such, click on the "Settings" tab of your Auth0 Application page, locate the "Application URIs" section, and fill in the following values:

Allowed Callback URLs
http://localhost:4040/api/auth/callback

The above value is the URL that Auth0 can use to redirect your users after they successfully log in. Since you are working in the context of a web application, you'll use server-side logic to handle the callback behavior using a Next.js API route. You'll set the auth API routes in the next section.

Allowed Logout URLs
http://localhost:4040

The above value is the URL that Auth0 can use to redirect your users after they log out.

Scroll down and click the "Save Changes" button.

Do not close this page yet. You'll need some of its information in the next section.

Add the Auth0 configuration variables to Next.js

From the Auth0 Application Settings page, you need the Auth0 Domain, Client ID, and Client Secret values to allow your Next.js web application to use the communication bridge you created.

What exactly is an Auth0 Domain, an Auth0 Client ID, and an Auth0 Client Secret?

Domain When you created a new Auth0 account, Auth0 asked you to pick a name for your Tenant. This name, appended with auth0.com, is your Auth0 Domain. It's the base URL that you will use to access the Auth0 APIs and the URL where you'll redirect users to log in.

You can also use custom domains to allow Auth0 to do the authentication heavy lifting for you without compromising your branding experience.

Client ID

Each application is assigned a Client ID upon creation, which is an alphanumeric string, and it's the unique identifier for your application (such as q8fij2iug0CmgPLfTfG1tZGdTQyGaTUA). You cannot modify the Client ID. You will use the Client ID to identify the Auth0 Application to which the Auth0 Next.js SDK needs to connect.

Client Secret

This secret protects your resources by only granting tokens to requestors if they're authorized. Think of it as your application's password, which must be kept confidential at all times. If anyone gains access to your Client Secret, they can impersonate your application and access protected resources.

Head back to your Auth0 application page and click on the "Settings" tab.

Locate the "Basic Information" section and follow these steps to get the Auth0 Domain, Auth0 Client ID, and Auth0 Client Secret values:

Auth0 application settings to enable user authentication

These variables let your Next.js web application identify itself as an authorized party to interact with the Auth0 authentication server.

When you enter a value in the input fields present on this page, any code snippet that uses such value updates to reflect it. Using the input fields makes it easy to copy and paste code as you follow along.

As such, enter the "Domain" and "Client ID" values in the following fields to set up your single-page application in the next section:

For security, these configuration values are stored in memory and only used locally. They are gone as soon as you refresh the page! As an extra precaution, you should use values from an Auth0 test application instead of a production one.

You'll also need a session secret to sign the session ID cookie of your Next.js web application. Use the following command to generate a random secret key:

COMMAND
node -e "console.log(crypto.randomBytes(32).toString('hex'))"

Copy the long-string output from the terminal and paste it into the following input box:

Now, create a .env file under the root project directory:

MACOS
WINDOWS
LINUX
touch .env

Populate the .env file under the Next.js project directory as follows:

.env
AUTH0_SECRET=KEY-VALUE
AUTH0_BASE_URL=http://localhost:4040
AUTH0_ISSUER_BASE_URL=https://AUTH0-DOMAIN
AUTH0_CLIENT_ID=AUTH0-CLIENT-ID
AUTH0_CLIENT_SECRET=AUTH0-CLIENT-SECRET

The AUTH0_BASE_URL is the base URL of your application.

Get the Auth0 Client Secret

Head back to the "Settings" tab of your Auth0 application page in the Auth0 Dashboard to get the value for AUTH0_CLIENT_SECRET.

Locate the "Client Secret" field, copy its value, and paste it as the value of the AUTH0_CLIENT_SECRET environment variable in the .env file.

This page does not store the value of the Auth0 Client Secret. Not even in memory. Think of it as your application's password, which must be kept confidential at all times. If anyone gains access to your Client Secret, they can impersonate your application and access protected resources. The Auth0 Client Secret protects your resources by only granting authentication-related credentials in the form of tokens to requestors if they're authorized.

Restart the development server so that your Next.js project becomes aware of these .env file changes. Stop the development server and then run the following command again:

COMMAND
npm run dev

Create a Dynamic Authentication API route

Recall that the Auth0 Callback URL points to http://localhost:4040/api/auth/callback, which is the URL that Auth0 uses to redirect your users after they successfully log in. As such, you need a set of API routes under an api/auth/ directory. Technically, you don't need to put API routes under an api/ directory when using the Next.js App Router paradigm; however, it's helpful to maintain that project structure convention for simplicity.

You can use the Auth0 Next.js SDK to implement user authentication in Next.js applications. So first and foremost, execute the following command to install the library:

COMMAND
npm install @auth0/nextjs-auth0
This library requires Node.js v16 or higher.

Now, follow these steps to create a dynamic Next.js API route that can handle all the authentication flows of your Next.js application:

  • Create an api directory under the src/app directory.

  • Create an auth directory under the newly created src/app/api directory.

  • In turn, create a [auth0] directory under the src/app/api/auth directory.

  • Now, create a route.ts file under the src/app/api/auth/[auth0] directory.

You can create all the folders and files above by running the following command:

MACOS
WINDOWS
LINUX
mkdir -p 'src/app/api/auth/[auth0]' && touch 'src/app/api/auth/[auth0]/route.ts'

Populate the src/app/api/auth/[auth0]/route.ts file, which is the path to your dynamic API route, as follows:

src/app/api/auth/[auth0]/route.ts
import { handleAuth } from '@auth0/nextjs-auth0';
export const GET = handleAuth();

The handleAuth() method is the main way to use the server-side features of the Auth0 Next.js SDK. Running handleAuth() automatically creates 5 API routes for you to handle the essential features of user authentication in Next.js:

  • /api/auth/login
    • This API route logs users into your Next.js application by redirecting them to the Auth0 Universal Login page.
  • /api/auth/callback
    • This is the API route to which Auth0 will redirect your users after they successfully log in.
  • /api/auth/logout
    • This API route logs out the user from your Next.js application.
  • /api/auth/me
    • This API route responds with user profile information in JSON format.
    • You can consume the data from this API route on the client side using the useUser() React Hook exposed by the Auth0 Next.js SDK.

Next.js is a full-stack framework for building web applications. In this section, you have set up the required logic to handle user authentication on the server side of your Next.js application. In the next section, you'll configure your Next.js application to consume user authentication data on the client side.

Configure an Authentication Provider for Next.js

Under the hood, the Auth0 Next.js SDK uses React Context to manage the authentication state of your users on the client side. As such, the SDK exposes the UserProvider component that provides a UserContext to its child components. You can wrap your App Router Root Layout component with UserProvider to integrate Auth0 with your Next.js app.

Update the src/app/layout.tsx file as follows:

src/app/layout.tsx
import "../styles/styles.css";
import React, { PropsWithChildren } from "react";
import { PageLayout } from "@/components/page-layout";
import { siteMetadata } from "@/components/page-head";
import { PreloadResources } from "@/app/preload-resources";
import { UserProvider } from "@auth0/nextjs-auth0/client";
export const metadata = siteMetadata;
const RootLayout: React.FC<PropsWithChildren> = ({ children }) => {
return (
<html lang="en">
<PreloadResources />
<body>
<UserProvider>
<PageLayout>{children}</PageLayout>
</UserProvider>
</body>
</html>
);
};
export default RootLayout;

You have completed setting up an authentication service that your Next.js application can consume. All that is left is for you to continue building up the starter project throughout this tutorial by implementing React components to trigger and manage the authentication flow.

Add User Login to Next.js

Creating login forms or login pages with React can be complex. You can save development time by using a login page hosted by Auth0 that has a built-in login form that supports different types of user authentication: username and password, social login, and Multi-Factor Authentication (MFA). You just need to create a button that takes users from your Next.js application to the login page.

Start by creating a buttons directory under the src/components directory:

COMMAND
mkdir src/components/buttons

Create a login-button.tsx file under the src/components/buttons directory:

MACOS
WINDOWS
LINUX
touch src/components/buttons/login-button.tsx

Populate src/components/buttons/login-button.tsx like so:

src/components/buttons/login-button.tsx
export const LoginButton = () => {
return (
<a className="button__login" href="/api/auth/login">
Log In
</a>
);
};

This button will redirect your users to the /api/auth/login API route to kickstart the user authentication process.

Let's go beyond this simple setup and command Auth0 to return users to the /profile page whenever they log in. Update your src/app/api/auth/[auth0]/route.ts file as follows:

src/app/api/auth/[auth0]/route.ts
import { handleAuth, handleLogin } from "@auth0/nextjs-auth0";
export const GET = handleAuth({
login: handleLogin({
returnTo: "/profile",
}),
});

You can use the handleLogin() method to customize the default login handler without overriding it using an options object. The returnTo property of this options object is the path that you want your users to return to after they successfully log in. By setting returnTo to /profile, you override the default return path, which is the Auth0 Base URL set in your .env file.

You can explore all the available customization options by visiting the LoginOptions document.

Add User Sign-Up to Next.js

Creating sign-up forms is much more complex. However, you can use a sign-up form hosted by Auth0 that has a built-in password strength verification.

You can create an API route that takes users from your Next.js application to the sign-up page by adding another property to the handleAuth() method in your dynamic API route.

Update your src/app/api/auth/[auth0]/route.ts file as follows:

src/app/api/auth/[auth0]/route.ts
import { handleAuth, handleLogin } from "@auth0/nextjs-auth0";
export const GET = handleAuth({
login: handleLogin({
returnTo: "/profile",
}),
signup: handleLogin({
authorizationParams: {
screen_hint: "signup",
},
returnTo: "/profile",
}),
});

You can further customize the behavior of the handleLogin() method by specifying an authorizationParams object. The Auth0 Next.js SDK will use the properties you define within the authorizationParams object as query parameters of the call to the Auth0 /authorize endpoint.

Consequently, you can make your users land directly on the sign-up page instead of the login page by specifying the screen_hint=signup query parameter when redirecting to /authorize as defined above.

To see this in practice, create a signup-button.tsx file under the src/components/buttons directory:

MACOS
WINDOWS
LINUX
touch src/components/buttons/signup-button.tsx

Populate src/components/buttons/signup-button.tsx like so to define a sign-up button component:

src/components/buttons/signup-button.tsx
export const SignupButton = () => {
return (
<a className="button__sign-up" href="/api/auth/signup">
Sign Up
</a>
);
};

Using the Auth0 Signup feature requires you to enable the Auth0 New Universal Login Experience in your tenant.

Open the Universal Login section of the Auth0 Dashboard and choose the "New" option under the "Experience" subsection.

Auth0 Universal Login Experience options

Scroll down and click on the "Save Changes" button.

The difference between the login and sign-up user experience will be more evident once you integrate those components with your Next.js application and see them in action. You'll do that in the following sections.

If a user clicks on the login or sign-up button and they already have an existing session, Auth0 will redirect them to your Next.js application using the callback URL. However, if you want to always show the login or sign up form regardless of session status, you can add the prompt=login to the authorizationParams object. Learn more.

Add User Logout to Next.js

You can log out users from your Next.js application by logging them out of their Auth0 sessions using the /api/auth/logout API route.

Create a logout-button.tsx file under the src/components/buttons directory:

MACOS
WINDOWS
LINUX
touch src/components/buttons/logout-button.tsx

Populate src/components/buttons/logout-button.tsx like so:

src/components/buttons/logout-button.tsx
export const LogoutButton = () => {
return (
<a className="button__logout" href="/api/auth/logout">
Log Out
</a>
);
};

When your users visit the /api/auth/logout API route, the Auth0 Next.js SDK clears the application session and redirects to the Auth0 /v2/logout endpoint to clear the Auth0 session under the hood.

As with the login method, you can also customize the logout behavior by using the handleLogout() property the configuration object of the handleAuth() method.

For example, you could define a returnTo option to specify the URL where Auth0 should redirect your users after they log out. Currently, you are working locally, and your Auth0 application's "Allowed Logout URLs" points to http://localhost:4040. However, if you were to deploy your Next.js application to production, you need to add the production logout URL to the "Allowed Logout URLs" list and ensure that Auth0 redirects your users to that production URL and not localhost.

A best practice when working with Auth0 is to have different tenants for your different project environments. For example, it's recommended for developers to specify a production tenant. A production tenant gets higher rate limits than non-production tenants. Check out the "Set Up Multiple Environments" Auth0 document to learn more about how to set up development, staging, and production environments in the Auth0 Identity Platform.

Render Next.js Components Based on Authentication

In this section, you'll learn how to render Next.js components conditionally based on the authentication status of your users.

Render the authentication buttons conditionally

The Next.js starter application features a desktop and mobile navigation experience.

When using your Next.js application on a viewport large enough to fix a desktop or tablet experience, you'll see a navigation bar at the top of the page.

When using a viewport that fits the screen constraints of a mobile device, you'll see a menu button at the top-right corner of the page. Tapping or clicking on the menu button opens a modal that shows you the different pages that you can access in the application.

In this section, you'll expose the button components that trigger the login, sign-up, and logout events through these page navigation elements.

Let's start with the desktop navigation user experience. You'll show both the login and sign-up buttons on the navigation bar when the user is not logged in. Naturally, you'll show the logout button when the user is logged in.

Update src/components/navigation/desktop/nav-bar-buttons.tsx as follows to implement the user experience defined above:

src/components/navigation/desktop/nav-bar-buttons.tsx
"use client";
import { useUser } from "@auth0/nextjs-auth0/client";
import React from "react";
import { SignupButton } from "@/components/buttons/signup-button";
import { LoginButton } from "@/components/buttons/login-button";
import { LogoutButton } from "@/components/buttons/logout-button";
export const NavBarButtons = () => {
const { user } = useUser();
return (
<div className="nav-bar__buttons">
{!user && (
<>
<SignupButton />
<LoginButton />
</>
)}
{user && (
<>
<LogoutButton />
</>
)}
</div>
);
};

You can determine if a user is authenticated by checking if the user object returned by the useUser() React Hook is defined. As such, you can rely on the user value to render UI elements conditionally depending on the authentication state of your users, as you did above.

The mobile navigation experience works similarly, except the mobile menu modal hides the authentication-related buttons while closed.

Update src/components/navigation/mobile/mobile-nav-bar-buttons.tsx as follows:

src/components/navigation/mobile/mobile-nav-bar-buttons.tsx
"use client";
import { useUser } from "@auth0/nextjs-auth0/client";
import React from "react";
import { SignupButton } from "@/components/buttons/signup-button";
import { LoginButton } from "@/components/buttons/login-button";
import { LogoutButton } from "@/components/buttons/logout-button";
export const MobileNavBarButtons = () => {
const { user } = useUser();
return (
<div className="mobile-nav-bar__buttons">
{!user && (
<>
<SignupButton />
<LoginButton />
</>
)}
{user && (
<>
<LogoutButton />
</>
)}
</div>
);
};

Go ahead and try to log in. Your Next.js application redirects you to the Auth0 Universal Login page. You can use the form to log in with a username and password or a social identity provider like Google. Notice that this login page also gives you the option to sign up.

New Auth0 Universal Login Experience Form

However, when you click the sign-up button from your application directly, Next.js takes you to the signup page, where your users can sign up for the Next.js application. Try it out!

New Auth0 Universal Login Experience Signup Page
You can customize the appearance of New Universal Login pages. You can also override any text in the New Experience using the Text Customization API.

Notice that when you finish logging in or signing up, Auth0 redirects you to the "Profile Page" of your Next.js application. Also, notice that the login and sign-up buttons may briefly show up before the logout button renders. You'll fix that next.

Render the application conditionally

The user interface flashes because your Next.js app doesn't know if Auth0 has authenticated the user yet. Your Next.js application will know the user authentication status after the Auth0 Next.js SDK initializes.

To fix that UI flashing, use the isLoading value from the useUser() React hook to render the PageLayout component once the Auth0 Next.js SDK has finished loading.

Open the src/components/page-layout.tsx file and update it as follows:

src/components/page-layout.tsx
"use client";
import React, { PropsWithChildren } from "react";
import { NavBar } from "./navigation/desktop/nav-bar";
import { MobileNavBar } from "./navigation/mobile/mobile-nav-bar";
import { PageFooter } from "./page-footer";
import { PageLoader } from "@/components/page-loader";
import { useUser } from "@auth0/nextjs-auth0/client";
export const PageLayout: React.FC<PropsWithChildren> = ({ children }) => {
const { isLoading } = useUser();
if (isLoading) {
return (
<div className="page-layout">
<PageLoader />
</div>
);
}
return (
<div className="page-layout">
<NavBar />
<MobileNavBar />
<div className="page-layout__content">{children}</div>
<PageFooter />
</div>
);
};

While the SDK is loading, the PageLoader component renders, which shows up an animation. Log out and log back in to see this in action. No more UI flashing should happen.

Render navigation tabs conditionally

There may be use cases where you want to hide user interface elements from users who have not logged in to your application. For this Next.js application, only authenticated users should see the navigation tabs to access the /protected and /admin pages.

To implement this use case, you'll again rely on the value of the user object from the useUser() React Hook.

Open the src/components/navigation/desktop/nav-bar-tabs.tsx component file that defines your desktop navigation tabs and update it like so:

src/components/navigation/desktop/nav-bar-tabs.tsx
import React from "react";
import { NavBarTab } from "./nav-bar-tab";
import { useUser } from "@auth0/nextjs-auth0/client";
export const NavBarTabs: React.FC = () => {
const { user } = useUser();
return (
<div className="nav-bar__tabs">
<NavBarTab path="/profile" label="Profile" />
<NavBarTab path="/public" label="Public" />
{user && (
<>
<NavBarTab path="/protected" label="Protected" />
<NavBarTab path="/admin" label="Admin" />
</>
)}
</div>
);
};

Next, open the src/components/navigation/mobile/mobile-nav-bar-tabs.tsx component file that defines your mobile navigation tabs and update it like so:

src/components/navigation/mobile/mobile-nav-bar-tabs.tsx
import React from "react";
import { MobileNavBarTab } from "./mobile-nav-bar-tab";
import { useUser } from "@auth0/nextjs-auth0/client";
export const MobileNavBarTabs: React.FC = ({}) => {
const { user } = useUser();
return (
<div className="mobile-nav-bar__tabs">
<MobileNavBarTab path="/profile" label="Profile" />
<MobileNavBarTab path="/public" label="Public" />
{user && (
<>
<MobileNavBarTab path="/protected" label="Protected" />
<MobileNavBarTab path="/admin" label="Admin" />
</>
)}
</div>
);
};

Log out from your Next.js application and notice how now you can only see the tabs for the /profile and /public pages in the navigation bar, along with the login and sign-up buttons. Log in and then see the rest of the navigation bar show up.

Keep in mind that this does not restrict access to the /admin and /protected pages at all. You'll learn how to use the Auth0 Next.js SDK to protect Next.js pages in the next section.

Use Middleware to Protect Next.js Pages

User authentication is a mechanism to control who can access your application. You can integrate your application with Auth0 to prevent users who have not logged in from accessing a /profile or /admin route.

If users want to access a protected page from your application, Auth0 will stop them and ask them to present their credentials. If Auth0 can verify who they are and that they are supposed to go in there, Auth0 will let them in.

The authentication process won't happen within your Next.js application layer when using Auth0. Your Next.js application will redirect your users to the Auth0 Universal Login page, where Auth0 asks for credentials and redirects the user back to your application with the result of the authentication process.

The Auth0 Next.js SDK remembers the route the user wanted to access and, if authentication were successful, it takes the user to that route.

You can leverage the power of Next.js middleware to protect pages using Auth0. Next.js middleware runs code before an incoming request to your application completes. You can then modify the response based on the nature of the incoming request to fulfill the business needs of your application. You can rewrite or redirect the response, modify the request or response headers, or respond directly.

Start by creating a middleware.ts file under the src directory:

MACOS
WINDOWS
LINUX
touch src/middleware.ts

Then, populate the src/middleware.ts file like so:

src/middleware.ts
import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0/edge";
export default withMiddlewareAuthRequired();

The middleware set up above will protect all your routes. If you visit the home page, http://localhost:4040/, you won't access its content until you log in.

Currently, you only want to protect two routes: /protected and /admin.

You can use a Next.js matcher to protect specific routes. Update the src/middleware.ts file as follows:

src/middleware.ts
import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0/edge";
export default withMiddlewareAuthRequired();
export const config = {
matcher: ["/protected", "/admin"],
};

A matcher lets you run Next.js Middleware only on specific paths. You can match a single path or multiple paths using a string array. You can also use powerful regular expressions to match incoming requests using negative lookaheads or character matching.

Now you can visit the home page, http://localhost:4040/, and access its content.

However, you can now test that the protected paths require users to log in before accessing them. If you are logged in, log out. Then, try to access the Protected page or the Admin page. If it works, Next.js redirects you to log in with Auth0.

Protect a Server-Side Rendered Page

Next.js Middleware is not the only option that you have to protect Next.js pages using Auth0. You can wrap a React Server Component (RSC) with the withPageAuthRequired() method to require users to authenticate before they can visit the component, which represents a server-side rendered page.

The Profile page component renders user information that you could consider private or sensitive. Hence, this component should only render if Auth0 has authenticated the user.

Update the src/app/profile/page.tsx file as follows to protect the page it defines:

src/app/profile/page.tsx
import { NextPage } from "next";
import React from "react";
import { CodeSnippet } from "@/components/code-snippet";
import Image from "next/image";
import { getUserProfileData } from "@/services/profile.service";
import { withPageAuthRequired } from "@auth0/nextjs-auth0";
const Profile: NextPage = withPageAuthRequired(
async () => {
const user = await getUserProfileData();
return (
<div className="content-layout">
<h1 id="page-title" className="content__title">
Profile Page
</h1>
<div className="content__body">
<p id="page-description">
<span>
<strong>Only authenticated users should access this page.</strong>
</span>
</p>
<div className="profile-grid">
<div className="profile__header">
<Image
src={user.picture}
alt="Profile"
className="profile__avatar"
width={80}
height={80}
/>
<div className="profile__headline">
<h2 className="profile__title">{user.name}</h2>
<span className="profile__description">{user.email}</span>
</div>
</div>
<div className="profile__details">
<CodeSnippet
title="User Profile Object"
code={JSON.stringify(user, null, 2)}
/>
</div>
</div>
</div>
</div>
);
},
{ returnTo: "/profile" },
);
export default Profile;

React Server Components are not aware of the req (request) object or url value of the protected page. So, if you want the user to return to the same page after login, you must specify the returnTo option.

If you visit the /profile page without a valid session, Next.js will redirect you to the login page.

Get Server-Side User Profile Information in Next.js

Currently, you are treating the "Profile" page as a server-side rendered page, which you are protecting with the withPageAuthRequired() method. Since you are operating in the context of the server, you can retrieve profile information directly in the server using the Auth0 session.

Update the src/services/profile.service.ts file as follows:

src/services/profile.service.ts
import "server-only";
import { Claims, getSession } from "@auth0/nextjs-auth0";
export const getUserProfileData = async (): Promise<Claims> => {
const session = await getSession();
if (!session) {
throw new Error(`Requires authentication`);
}
const { user } = session;
return user;
};

The getSession() method gets the user's session from the server. If the session is not defined, you throw an error that the nearest error bounder should catch.

In this project, there is only one error UI boundary defined in the src/app/error.tsx file. Following the Next.js App Router paradigm, you create error.tsx|jsx files within your route directories to define error UI boundaries.

If the session is defined, you extract the user object and return it. Then, the Profile page component uses properties of the user object (such as picture, name, and email) to personalize the user interface. Since the data comes from a simple object, you don't have to fetch it using asynchronous calls.

You also display the full content of the user object within a code box. You can now see all the other properties available for you to use.

If you are logged in to your application, visit http://localhost:4040/profile to see your user profile details.

Consume Client-Side User Profile Information in Next.js

As an alternative, you can also treat the Profile page as a client-side page and access the user profile information through the user object exposed via the useUser() React hook.

Start by using the Next.js middleware to also protect the /profile path. Update the src/middleware.ts file as follows:

src/middleware.ts
import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0/edge";
export default withMiddlewareAuthRequired();
export const config = {
matcher: ["/protected", "/admin", "/profile"],
};

Now, update the src/app/profile/page.tsx file like so:

src/app/profile/page.tsx
"use client";
import { NextPage } from "next";
import React from "react";
import { CodeSnippet } from "@/components/code-snippet";
import Image from "next/image";
import { useUser } from "@auth0/nextjs-auth0/client";
const Profile: NextPage = () => {
const { user } = useUser();
if (!user) {
return null;
}
return (
<div className="content-layout">
<h1 id="page-title" className="content__title">
Profile Page
</h1>
<div className="content__body">
<p id="page-description">
<span>
<strong>Only authenticated users should access this page.</strong>
</span>
</p>
<div className="profile-grid">
<div className="profile__header">
{user.picture && (
<Image
src={user.picture}
alt="Profile"
className="profile__avatar"
width={80}
height={80}
/>
)}
<div className="profile__headline">
<h2 className="profile__title">{user.name}</h2>
<span className="profile__description">{user.email}</span>
</div>
</div>
<div className="profile__details">
<CodeSnippet
title="User Profile Object"
code={JSON.stringify(user, null, 2)}
/>
</div>
</div>
</div>
</div>
);
};
export default Profile;

The useUser React Hook is only available on the client side or browser. As it is a React Hook powered by useState, you can only use it in client components in the context of Next.js App Router. As such, the first change in the Profile component is that you add the "use client" directive at the top of the file to transform this component from a server component to a client component.

Once you are operating in the context of a client component, you get the user object from the useUser() React Hook, being careful to check if it's defined to avoid improperly rendering the user data in the page.

Whether you get the user profile information on the server or the client side is up to the needs of your application and your security threat model.

Authentication Beyond Passwords: Try Passkeys Today

So far, you have seen how a user can sign up or log in to your application with a username and password. However, you can free your users from having to remember yet another password by allowing them to use passkeys as a new way to log in.

Passkeys are a phishing-resistant alternative to traditional authentication factors, such as the username/password combo, that offer an easier and more secure login experience to users.

You don't have to write any new code to start using passkeys in your application. You can follow the "Authentication with Passkeys" lab to learn how to enable passkeys in your Auth0 tenant and learn more about this emerging technology. Once you complete that optional lab, you can come back to this guide to continue learning about how to access protected API resources on behalf of a user from your application.

A form modal giving you information on how a passkey works and the option to create a passkey

Integrate Next.js with an API Server

This section shows you how to get an access token in your Next.js application and how to use an access token to make API calls to protected API endpoints.

When you use Auth0, you delegate the authentication process to a centralized service. Auth0 provides you with functionality to log in and log out users from your Next.js application. However, your application may need to access protected resources from an API.

You can also protect an API with Auth0. There are multiple API quickstarts to help you integrate Auth0 with your backend platform.

When you use Auth0 to protect your API, you also delegate the authorization process to a centralized service that ensures only approved client applications can access protected resources on behalf of a user.

How can you make secure API calls from Next.js?

Your Next.js application authenticates the user and can request an access token from Auth0. The application can then pass that access token to your API as a credential. In turn, your API can use Auth0 libraries to verify the access token it receives from the calling application and issue a response with the desired data.

Instead of creating an API from scratch to test the authentication and authorization flow between the client and the server or server to server, you can pair this web application with an API server that matches the technology stack you use at work. The Next.js "Hello World" web application that you have been building up can interact with any of the "Hello World" API server samples from the Auth0 Developer Center.

Pick an API code sample in your preferred backend framework and language from the list below and follow the instructions on the code sample page to set it up. Once you complete the sample API server setup, please return to this page to learn how to integrate that API server with your Next.js application.

actix-web
rust
Actix Web/Rust API:Authorization Code Sample
Code sample of a simple Actix Web server that implements token-based authorization using Auth0.
aspnet-core
csharp
ASP.NET Core Code Sample:Web API Authorization
Code sample of a simple ASP.NET Core server that implements token-based authorization using Auth0.
aspnet-core
csharp
ASP.NET Core v5 Code Sample:Web API Authorization
Code sample of a simple ASP.NET Core v5.0 server that implements token-based authorization using Auth0.
django
python
Django/Python API:Authorization Code Sample
Code sample of a simple Django server that implements token-based authorization using Auth0.
express
javascript
Express.js Code Sample:Basic API Authorization
Code sample of a simple Express.js server that implements token-based authorization using Auth0.
express
typescript
Express.js/TypeScript Code Sample:Basic API Authorization
Code sample of a simple Express.js server built with TypeScript that implements token-based authorization using Auth0.
fastapi
python
FastAPI/Python Code Sample:Basic API Authorization
Code sample of a simple FastAPI server that implements token-based authorization using Auth0.
flask
python
Flask/Python API:Authorization Code Sample
Code sample of a simple Flask server that implements token-based authorization using Auth0.
laravel
php
Laravel/PHP Code Sample:Basic API Authorization with Auth0 Laravel SDK
Code sample of a simple Laravel server that implements token-based authorization using the Auth0 Laravel SDK.
laravel
php
Laravel/PHP Code Sample:Basic API Authorization with Auth0 PHP SDK
Code sample of a simple Laravel server that implements token-based authorization using the Auth0 PHP SDK.
lumen
php
Lumen Code Sample:Basic API Authorization
Code sample of a simple Lumen server that implements token-based authorization using Auth0.
nestjs
typescript
NestJS Code Sample:Basic API Authorization
Code sample of a simple NestJS server that implements token-based authorization using Auth0.
phoenix
elixir
Phoenix/Elixir API:Authorization Code Sample
Code sample of a simple Phoenix server that implements token-based authorization using Auth0.
rails
ruby
Ruby on Rails API:Authorization Code Sample
Code sample of a simple Rails server that implements authorization using Auth0.
spring
java
Spring Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring Web API server to protect API endpoints, using Spring Security and the Okta Spring Boot Starter.
spring
java
Spring Functional Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring Web API server to protect API endpoints, following a functional approach.
spring-webflux
java
Spring WebFlux Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring WebFlux API server to protect API endpoints, using Spring Security and the Okta Spring Boot Starter.
standard-library
golang
Golang Code Sample:Basic API Authorization
Code sample of a simple Golang server that implements token-based authorization using Auth0.
symfony
php
Symfony Code Sample:Basic API Authorization
Code sample of a simple Symfony server that implements token-based authorization using Auth0.

Call a Protected API from Next.js

Once you have set up the API server code sample, you should have created an Auth0 Audience value. Store that value in the following field so that you can use it throughout the instructions presented on this page easily:

Keep in mind that the "Hello World" API server samples run on http://localhost:6060 by default. You can define that API base URL as a variable in your .env file and then reference it whenever you need to make a request to the external API server.

Consequently, update the .env file under the Next.js project directory as follows:

.env
AUTH0_SECRET=KEY-VALUE
AUTH0_BASE_URL=http://localhost:4040
AUTH0_ISSUER_BASE_URL=https://AUTH0-DOMAIN
AUTH0_CLIENT_ID=AUTH0-CLIENT-ID
AUTH0_CLIENT_SECRET=AUTH0-CLIENT-SECRET
AUTH0_AUDIENCE=AUTH0-AUDIENCE
API_SERVER_URL=http://localhost:6060

You are using AUTH0_AUDIENCE to add the value of your Auth0 API Audience so that your Next.js application can request resources from the API that such audience value represents.

Restart your development server so that your Next.js project can become aware of these .env file changes. Stop the development server and run the following command again:

COMMAND
npm run dev

Let's understand better what the AUTH0_AUDIENCE and API_SERVER_URL values represent.

The API_SERVER_URL is the URL where your sample API server listens for requests. In production, you'll change this value to the URL of your live server. Remember that since you are using a Next.js application that uses the App Router paradigm, you can access that API_SERVER_URL environment variable from the logic of any React Server Component or API route. There's no need to append NEXT_PUBLIC_ to it, as you won't make any requests to the server in client components.

Your Next.js web application must pass an access token when it calls a target API to access protected resources. You can request an access token in a format that the API can verify by passing the audience to the Auth0 Next.js SDK.

The value of the Auth0 Audience must be the same for both the Next.js web application and the external API server you decided to set up.

Why is the Auth0 Audience value the same for both apps? Auth0 uses the audience value to determine which resource server (API) the user is authorizing your Next.js web application to access. It's like a phone number. You want to ensure that your Next.js application "texts the right API".

What about using scopes?

In the context of API resources, you can also define a scope. When you don't pass a scope option to Auth0 Next.js SDK, the SDK defaults to using the OpenID Connect Scopes: openid profile email.

  • openid: This scope informs the Auth0 Authorization Server that the Client is making an OpenID Connect (OIDC) request to verify the user's identity. OpenID Connect is an authentication protocol.

  • profile: This scope value requests access to the user's default profile information, such as name, nickname, and picture.

  • email: This scope value requests access to the email and email_verified information.

The details of the OpenID Connect Scopes go into an ID Token. However, you can define custom API scopes to implement access control. You'll identify those custom scopes in the calls that your web applications make to that API. Auth0 includes API scopes in the access token as the scope claim value.

The concepts about API scopes or permissions are better covered in an Auth0 API tutorial such as "Use TypeScript to Create a Secure API with Node.js and Express: Role-Based Access Control".

The Auth0 Next.js SDK provides you with a method to get an access token from a React Server Component (RSC): getAccessToken().

It's time to update the /protected and /admin page components to let users retrieve private data from the API server. Please ensure that your sample API server is running as you complete the following steps.

Start by updating the service methods present in the src/services/message.service.ts file as follows to request an access token from Auth0:

src/services/message.service.ts
import "server-only";
import { Message } from "@/models/message";
import { getAccessToken } from "@auth0/nextjs-auth0";
interface GetMessageParams {
type: "protected" | "admin";
}
export const getMessage = async ({
type,
}: GetMessageParams): Promise<Message> => {
const { accessToken } = await getAccessToken();
if (!accessToken) {
throw new Error(`Requires authorization`);
}
const res = await fetch(
`${process.env.API_SERVER_URL}/api/messages/${type}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);
if (!res.ok) {
const json = await res.json();
return {
text: json.message || res.statusText || "Unable to fetch message.",
};
}
return res.json();
};
export const getPublicMessage = async () => {
return {
text: "This is a public message.",
};
};
export const getProtectedMessage = async () => {
return getMessage({ type: "protected" });
};
export const getAdminMessage = async () => {
return getMessage({ type: "admin" });
};

You are updating the logic of the getMessage() helper method to use the getAccessToken() method to obtain an access token from the Auth0 authentication server. You then pass that access token value, if defined, as an Authorization header in the fetch() request.

If you were to try this out using your access token right now, it would not work. You need to log out completely and log back in for Auth0 to get a new request to its /authorize endpoint that includes the audience parameter so that it can issue you a valid access token.

After that log-out/log-in operation, visit the "Protected" page or "Admin" page and validate that you are getting the message data from the external API server. If the access token is not valid or not working, you'll see an error message.

That's all it takes to integrate Next.js with an external API server directly from a React Server Component when that external API server is also secured by Auth0. You can also get an access token from a Next.js API route handler, including the Edge Runtime.

Next Steps

You have implemented user authentication in Next.js following an approach focused on the new App Router implementation. You have a Next.js application that can identify your users, get user profile information, and control the content your users can access by protecting routes and API resources.

This tutorial covered the most common authentication use case for a Next.js web application: simple login and logout. However, Auth0 is an extensible and flexible identity platform that can help you achieve even more. If you have a more complex use case, check out the Auth0 Architecture Scenarios to learn more about the typical architecture scenarios we have identified when working with customers on implementing Auth0.