A step-by-step walkthrough of building an authenticated Next.js app with WorkOS AuthKit.
In this tutorial, we’ll build a Next.js application with signup, sign-in, a protected page, and sign-out – all powered by AuthKit. By the end, we’ll have a working authenticated app running locally.
Time: ~15 minutes
Stack: Next.js (App Router) + @workos-inc/authkit-nextjs
Let’s start with a fresh Next.js project:
npx create-next-app@latest authkit-demo --typescript --app --eslint --tailwind --src-dir --import-alias "@/*" cd authkit-demo
Accept the defaults for any prompts. Once it finishes, let’s make sure it runs:
npm run dev
Open http://localhost:3000 and we should see the Next.js welcome page.
Now let’s add the AuthKit Next.js SDK:
npm install @workos-inc/authkit-nextjs
We need three values from the WorkOS dashboard. Create a .env.local file in the project root:
WORKOS_API_KEY="sk_test_..." # from API Keys in the dashboard WORKOS_CLIENT_ID="client_..." # from API Keys in the dashboard WORKOS_COOKIE_PASSWORD="" # we'll generate this next NEXT_PUBLIC_WORKOS_REDIRECT_URI="http://localhost:3000/callback"
Generate a strong cookie password:
openssl rand -base64 32
Paste the output as the WORKOS_COOKIE_PASSWORD value.
In the WorkOS Dashboard, go to Redirects and add http://localhost:3000/callback as a redirect URI. Set it as the default.
While we’re here, set the Sign-in endpoint to http://localhost:3000/login.
In the WorkOS Dashboard Overview section, click Set up AuthKit and follow the instructions to activate it.
Let’s wrap our app layout with the AuthKitProvider. Open src/app/layout.tsx and update it:
import type { Metadata } from 'next'; import { AuthKitProvider } from '@workos-inc/authkit-nextjs'; import './globals.css'; export const metadata: Metadata = { title: 'AuthKit Demo', description: 'A demo app with WorkOS AuthKit', }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <AuthKitProvider>{children}</AuthKitProvider> </body> </html> ); }
Create a proxy.ts file in the project root (next to package.json):
import { authkitMiddleware } from '@workos-inc/authkit-nextjs'; export default authkitMiddleware();
This handles session management and redirects automatically.
When a user signs in via AuthKit, they’re redirected to our callback route. Create it at src/app/callback/route.ts:
import { handleAuth } from '@workos-inc/authkit-nextjs'; export const GET = handleAuth();
We need a route that redirects users to AuthKit for sign-in. Create src/app/login/route.ts:
import { signIn } from '@workos-inc/authkit-nextjs'; export const GET = signIn;
Now let’s replace the default home page with one that shows the user’s info or a sign-in link. Update src/app/page.tsx:
import { withAuth, getSignInUrl, getSignUpUrl, } from '@workos-inc/authkit-nextjs'; import Link from 'next/link'; export default async function HomePage() { const { user } = await withAuth(); if (!user) { const signInUrl = await getSignInUrl(); const signUpUrl = await getSignUpUrl(); return ( <main style={{ padding: '2rem', fontFamily: 'system-ui' }}> <h1>Welcome to AuthKit Demo</h1> <p>Sign in or create an account to get started.</p> <div style={{ display: 'flex', gap: '1rem', marginTop: '1rem' }}> <Link href={signInUrl}>Sign In</Link> <Link href={signUpUrl}>Sign Up</Link> </div> </main> ); } return ( <main style={{ padding: '2rem', fontFamily: 'system-ui' }}> <h1>Welcome, {user.firstName}!</h1> <p>Email: {user.email}</p> <div style={{ marginTop: '1rem' }}> <Link href="/dashboard">Go to Dashboard</Link> </div> </main> ); }
Let’s try it out. Restart the dev server (npm run dev), go to http://localhost:3000, and we should see the sign-in and signup links.
Click Sign Up, create an account through AuthKit, and after redirecting back, we should see our name and email on the page.
Let’s create a dashboard page that only signed-in users can access. Create src/app/dashboard/page.tsx:
import { withAuth } from '@workos-inc/authkit-nextjs'; import Link from 'next/link'; export default async function DashboardPage() { const { user } = await withAuth({ ensureSignedIn: true }); return ( <main style={{ padding: '2rem', fontFamily: 'system-ui' }}> <h1>Dashboard</h1> <p>This page is only visible to signed-in users.</p> <h2>Your profile</h2> <ul> <li> <strong>Name:</strong> {user.firstName} {user.lastName} </li> <li> <strong>Email:</strong> {user.email} </li> <li> <strong>ID:</strong> {user.id} </li> </ul> <div style={{ marginTop: '1rem' }}> <Link href="/">Back to Home</Link> </div> </main> ); }
The ensureSignedIn: true option means unauthenticated users are automatically redirected to AuthKit to sign in.
Visit http://localhost:3000/dashboard – if we’re signed in, we see the profile. If not, we’re redirected to sign in first.
Let’s add a sign-out button. Update the dashboard page to include it:
import { withAuth, getLogoutUrl } from '@workos-inc/authkit-nextjs'; import Link from 'next/link'; export default async function DashboardPage() { const { user } = await withAuth({ ensureSignedIn: true }); const logoutUrl = await getLogoutUrl(); return ( <main style={{ padding: '2rem', fontFamily: 'system-ui' }}> <h1>Dashboard</h1> <p>This page is only visible to signed-in users.</p> <h2>Your profile</h2> <ul> <li> <strong>Name:</strong> {user.firstName} {user.lastName} </li> <li> <strong>Email:</strong> {user.email} </li> <li> <strong>ID:</strong> {user.id} </li> </ul> <div style={{ display: 'flex', gap: '1rem', marginTop: '1rem' }}> <Link href="/">Back to Home</Link> <a href={logoutUrl}>Sign Out</a> </div> </main> ); }
Make sure to configure a Sign-out redirect in the WorkOS dashboard under Redirects. Set it to http://localhost:3000 so users return to the home page after signing out.
Click Sign Out to end the session. We should be redirected back to the home page in a signed-out state.
We now have a working Next.js app with: