Integrating Auth0 with Next.js for Authentication
Quickly set up authentication in a Next.js app with Auth0 so you don't have to code it yourself.
Sam Magura
Building authentication into your app from scratch is a lot of work, especially if you want to support multiple authentication methods — e.g. allowing the user to sign in with their Google account. Your team can save a ton of work by delegating authentication to a third-party service like Auth0 , a comprehensive authentication solution that can be integrated into almost any app.
The most common flow when using Auth0 is that the user clicks a "log in" button in your web app and they get redirected to a login page that's hosted on Auth0's servers. After the user logs in or creates a new account, Auth0 will call your application's "auth callback" with the user's info.
In this blog post, we'll walk through implementing Auth0 authentication in a Next.js web application. The Zero secrets manager will be used to securely retrieve the Auth0 client credentials from the cloud when the application starts up.
🔗 The full code for this example is available in the zerosecrets/examples GitHub repository.
Secure your secrets conveniently
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.
Creating the Auth0 Application
The Auth0 team has made our job really easy by providing an SDK for Next.js along with a step-by-step guide for using it. This post will roughly follow the official integration guide, with some changes to support integrating the Auth0 SDK with Zero.
As stated in the integration guide, the first step is to log in to Auth0. This will create a new Auth0 tenant, if you don't already have one. Once you have reached the Auth0 dashboard, create a new application. Select "Regular web application" for the type, and select Next.js for the technology.
With the application created, switch to the Settings tab of the application screen. The following fields should be copied into a new secret within Zero:
- Domain
- Client ID
- Client secret
- Secret - a random string you can generate by running
openssl rand -hex 32
in the terminal
Make sure to copy the project's Zero token to a file on your local PC.
Return to the application settings page in Auth0. Set http://localhost:3000/api/auth/callback
as an allowed callback URL, and set http://localhost:3000/
as an allowed logout URL. If this was a production tenant, you would replace localhost:3000
with your real domain name.
Creating the Next.js App
Now that the Auth0 configuration is complete, bootstrap a new Next.js application with the command
npx create-next-app@latest auth0-app
Make sure to select the App Router at the relevant prompt. I also opted to use Tailwind CSS.
Now, cd
into the project's directory and install the Auth0 Next.js SDK and the Zero TypeScript SDK :
npm install @auth0/nextjs-auth0 @zerosecrets/zero
Initializing the Auth0 SDK
This is where our walkthrough deviates from the official integration guide provided by Auth0. Their guide configures the Auth0 SDK using environment variables in a .env
file. Instead of using .env
, we will fetch the secrets from Zero at runtime. This requires us to initialize the Auth0 SDK using the initAuth0
function instead of relying on the automatic initialization mechanism.
Create a new file called util/auth0.ts
to initialize the SDK. In this file, we implement the standard pattern for fetching secrets from Zero and passing them to a 3rd party SDK:
import {initAuth0} from '@auth0/nextjs-auth0'
import {zero} from '@zerosecrets/zero'
export async function getAuth0() {
if (!process.env.ZERO_TOKEN) {
throw new Error('Did you forget to set the ZERO_TOKEN environment variable?')
}
const secrets = await zero({
token: process.env.ZERO_TOKEN,
pick: ['auth0'],
}).fetch()
if (!secrets.auth0) {
throw new Error('Did not receive Auth0 secrets.')
}
return initAuth0({
secret: secrets.auth0.secret,
baseURL: 'http://localhost:3000',
issuerBaseURL: `https://${secrets.auth0.domain}`,
clientID: secrets.auth0.client_id,
clientSecret: secrets.auth0.client_secret,
})
}
Creating the Dynamic Route Handler
The API methods forlogin, logout, and the auth callback are handled by the Auth0 SDK. To hook these API methods up, create a new route handler at app/api/auth/[auth0]/route.ts
. Here, [auth0]
is a dynamic route segment that can match any string.
The code for the route handler makes use of the getAuth0
function we created in the previous section:
import {getAuth0} from '../../../../../util'
const auth0 = await getAuth0()
export const GET = auth0.handleAuth()
This creates the following API methods:
/api/auth/login
/api/auth/logout
/api/auth/callback
/api/auth/me
Since we're using a top-level await
statement, you'll need to tweak your TSConfig to avoid a compiler error. In tsconfig.json
, set target: "es2020"
to enable support for top-level await
.
Now's a good time to run your application to make sure it starts up successfully. Remember to provide the Zero token as an environment variable so the secrets can be retrieved from the cloud:
ZERO_TOKEN='...' npm run dev
Building the Frontend
Let's edit the homepage, app/page.tsx
, to add log in / log out buttons and a message that displays the currently-signed in user, if authentication was successful. To create a log in button, you'll use a plain <a>
tag like this:
<a href="/api/auth/login">Log in</a>
Don't use the Next.js <Link>
component — that won't work.
Upon clicking the login button, you'll be redirected to the Auth0 login page. Register for a new account, and you'll be redirected back to the Next.js application.
Displaying the Current User
Now, we'd like to display the current user. There are two ways to access the current user — <UserProvider>
+ useUser
for client components, and getSession
for server components. I'll be using client components in my demo. There is an example of getSession
in the official Auth0 integration guide .
UserProvider
is a React context provider that should be rendered at the top of the component tree. To add it to your app, edit the root layout (app/layout.tsx
) like so:
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<UserProvider>
<body className={inter.className}>{children}</body>
</UserProvider>
</html>
)
}
Now, return to app/page.tsx
to display the current user, and conditionally render "Log in" or "Log out" based on whether the user is signed in.
'use client'
import {useUser} from '@auth0/nextjs-auth0/client'
export default function Home() {
const userContext = useUser()
return (
<main>
<h1 className="text-4xl mb-6 font-bold">Next.js Auth0 App</h1>
<p className="mb-4">
{userContext.user ? `You are logged in as ${userContext.user.email}` : 'You are not logged in.'}
</p>
<p>
{userContext.user ? (
<a href="/api/auth/logout" className="text-blue-600 underline">
Log out
</a>
) : (
<a href="/api/auth/login" className="text-blue-600 underline">
Log in
</a>
)}
</p>
</main>
)
}
Here are screenshots of my finished demo:
Wrapping Up
Integrating Auth0 with Next.js is very straightforward, thanks to the Auth0 Next.js SDK. It would be much more work to accomplish the same thing without this SDK. We also benefitted from using Zero to pull down multiple secrets from the cloud when the app starts up, so we don't have to store them in a .env
file.
Other articles
Use Pusher to Implement Real-Time Notifications
Pusher makes it easy to add pub-sub functionality to your web apps, allowing you to implement chat, notifications, and more.
Using Notion as a Human-Readable Database
Capture form submissions from your web app and store them where your team works.
Secure your secrets
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.