Next.js & Passkeys: This tutorial shows how to integrate passkeys into Next.js apps by implementing passkey-first authentication & session management.
Lukas
Created: July 4, 2023
Updated: February 17, 2025
We aim to make the Internet a safer place using passkeys. That's why we want to support developers with tutorials on how to implement passkeys.
In this blog post, we’ll explore how to implement Next.js passkeys for secure authentication using Corbado. We'll cover the setup of the Corbado React package for the Next.js frontend part as well as the Corbado Node.js SDK for the Next.js backend part.
If you want to see the finished code, please have a look at our Next.js passkey example repository on GitHub.
The final authentication page will look as follows:
This tutorial assumes basic familiarity with Next.js, HTML, CSS and JavaScript / TypeScript. Moreover, you need to have Node and NPM installed on your machine. Let's dive in!
Let's first discuss the structure of our project (full Next.js passkeys GitHub repo):
next.js/ ├── app/ │ ├── auth/ │ │ └── page.tsx │ ├── profile/ │ │ └── page.tsx │ ├── user-data/ │ │ └── route.ts │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ └── _utils/ │ ├── LogoutButton.tsx │ ├── Provider.tsx │ ├── QueryClientWrapper.tsx │ └── nodeSdk.ts └── .env.example
The rest of the files of this project can be ignored for the purpose of this tutorial.
In the following, we explain step-by-step what needs to be done to successfully set up the Next.js project. If you want to see the finished code, please have a look at our Next.js passkey example app repository.
Let's start by initializing a new Next.js project:
npx create-next-app@latest
In the installation guide steps, we select the following:
To set up Corbado passkey authentication in your Next.js project, follow the next steps.
Visit the Corbado developer panel to sign up and create your account (you'll see the passkey sign-up in action here!).
After sign-up, a project wizard will guide you through the necessary steps to get everything up and running:
Application URL
and Relying Party ID
. The Application URL
is the URL where you embed the Corbado UI component. In this example, we set it to http://localhost:3000
. The Relying Party ID
is the domain (no protocol, no port and no path) where passkeys should be bound to. Here, it's localhost
(you can define both values als in the Settings > General > URLs of the Corbado developer panel).Ben Gould
Head of Engineering
I’ve built hundreds of integrations in my time, including quite a few with identity providers and I’ve never been so impressed with a developer experience as I have been with Corbado.
10,000+ devs trust Corbado & make the Internet safer with passkeys. Got questions? We’ve written 150+ blog posts on passkeys.
Join Passkeys CommunityAfterwards, you'll see the code snippets you need to integrate into the Next.js project. The subsequent sections of this article will explain them in detail.
As another step, we create an API secret
which will be needed to request user data from the
Corbado backend. Please create an API secret
in Settings > Credentials > API secrets.
Afterwards, we need to also get the Frontend API
and Backend API
URLs which we can find
in Settings > General.
Next, you need to go back to your terminal and should navigate to the root directory of the project:
cd passkeys-demo-nextjs
Create a new .env
file at the root of your project. Copy and paste the API secret
, Project ID
as well as both Frontend API
and Backend API
URLs (you can obtain it from the Corbado developer panel from Settings > General). The .env
file should look like this now:
NEXT_PUBLIC_CORBADO_PROJECT_ID=<your-project-id> CORBADO_API_SECRET=<your-api-secret> CORBADO_FRONTEND_API=<your-frontend-api-url> CORBADO_BACKEND_API=<your-backend-api-url>
Next.js enables the straightforward use of environment variables through
process.env.ENV_VARIABLE_NAME
. Those variables prefixed with NEXT_PUBLIC_
can
be accessed in both client-side and server-side routes, as Next.js
automatically embeds these into the JavaScript bundle during the build process.
On the other hand, environment variables without this prefix are treated as a secret and can only be accessed in server-side routes. This is because Next.js does not expose these variables to the client-side to ensure the confidentiality of potentially sensitive information.
To style the application, you can copy our CSS styling or just create your own with tailwind. This is an optional step.
First of all, we need to install the Corbado React package as well as the Corbado Node.js SDK.
npm install @corbado/react @corbado/node-sdk
Optionally, you can also install the Corbado types for a smoother TypeScript experience:
npm i -D @corbado/types
We now create a wrapper around the <CorbadoProvider/>
component to render it
on the client-side.
"use client" import { CorbadoProvider } from "@corbado/react" export default function Provider({ children, }: { children: React.ReactNode }) { return ( <CorbadoProvider projectId={process.env.NEXT_PUBLIC_CORBADO_PROJECT_ID!} darkMode='off' setShortSessionCookie={true} > {children} </CorbadoProvider> ) }
We use the setShortSessionCookie
setting to create a cookie called
cbo_short_session
. This cookie is returned after a successful authentication event and is used on backend calls that we'll implement later.
Now, integrate the Provider
wrapper into your root layout:
import "./globals.css" import type {Metadata} from "next" import {Inter} from "next/font/google" import CorbadoProvider from "./_utils/Provider" const inter = Inter({subsets: ["latin"]}) export const metadata: Metadata = { title: "Next Corbado Example", description: "Go passwordless with Corbado and Next.js", } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang='en'> <CorbadoProvider> <body className={inter.className}>{children}</body> </CorbadoProvider> </html> ) }
Clean up the skeleton from the page.tsx
file under app/
and display the <CorbadoAuth/>
component. Include the 'use client'
directive so the page renders on the client-side.
It takes an onLoggedIn
callback which redirects to /profile
in our case.
'use client' import { CorbadoAuth } from "@corbado/react" import { useRouter } from "next/navigation" export default function Auth() { const router = useRouter() const onLoggedIn = () => { router.push("/profile") } return ( <div> <CorbadoAuth onLoggedIn={onLoggedIn} /> </div> ) }
Now, you have successfully implemented everything necessary to handle the sign-up and login.
Let's create a utility to access the Corbado.js Node SDK throughout our
application under app/_utils/nodeSdk.ts
.
import { Config, SDK } from '@corbado/node-sdk'; const projectID = process.env.NEXT_PUBLIC_CORBADO_PROJECT_ID!; const apiSecret = process.env.CORBADO_API_SECRET!; const frontendAPI = process.env.CORBADO_FRONTEND_API!; const backendAPI = process.env.CORBADO_BACKEND_API!; const cboConfig = new Config(projectID, apiSecret, frontendAPI, backendAPI); const sdk = new SDK(cboConfig); export default function getNodeSDK() { return sdk; }
Now, we have convenient access to the Corbado Node.js SDK throughout our app.
Additionally, we want to access user information in a server-side
rendered route with our profile page. Create a page.tsx
file under
app/profile/
and access the user's cbo_short_session
cookie, which is a JWT.
We redirect the user to the /
route if he isn't authenticated.
If he is authenticated, we render some of his profile information.
import {cookies} from "next/headers"; import getNodeSDK from "@/app/_utils/nodeSdk"; import {redirect} from "next/navigation"; import LogoutButton from "@/app/_utils/LogoutButton"; import PasskeyList from "@/app/_utils/PasskeyList"; // the user data will be retrieved server side export default async function Profile() { const cookieStore = cookies(); const session = cookieStore.get("cbo_short_session"); if (!session) { return redirect("/"); } const sdk = getNodeSDK(); let user; try { user = await sdk.sessions().validateToken(session.value); } catch { return redirect("/"); } return ( <div> <h1>Profile Page</h1> <p> User-ID: {user.getID()}<br/> Email: {user.getEmail()} </p> <LogoutButton/> <PasskeyList/> </div> ) }
As you can see, we also imported a <LogoutButton/>
component which needs to be
in another file as it utilizes onClick
functionality to log the user out.
Place this one inside app/_utils/LogoutButton.tsx
'use client' import { useCorbado } from "@corbado/react" import { useRouter } from "next/navigation" export default function LogoutButton() { const { logout } = useCorbado() const router = useRouter() const onLogout = () => { logout() router.push("/") } return ( <button onClick={onLogout}>Logout</button> ) }
Furthermore, we used the <PasskeyList/>
, which is a component provided by Corbado to manage a user's passkeys.
Here, we need to create a client wrapper once more under app/_utils/PasskeyList.tsx
:
"use client" import { PasskeyList as CorbadoPasskeyList } from "@corbado/react"; export default function PasskeyList() { return ( <CorbadoPasskeyList /> ) }
If everything is set up and installed, run the application with
npm run dev
You should see the following screen when navigating to http://localhost:3000
:
After a successful sign-up / login, you'll see the profile page at http://localhost:3000/profile
:
This tutorial showed how easy it is to add passkeys to a Next.js app using Corbado. Besides the easy passkey-first authentication integrated with the React package, Corbado provides simple and secure session management to access protected user data. If you want to read more about how Corbado's session management works, please check the docs here.
Table of Contents
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.
🚀 Subscribe to Substack
Get the latest news, strategies, and insights about passkeys sent straight to your inbox.