Skip to main content
This guide will walk you through setting up a complete authentication flow in your Next.js application using AuthKit.
Make sure you’ve completed the installation steps before starting this quickstart.

Step 1: Create the callback route

AuthKit redirects users back to your app after authentication. Create a route handler to process this callback. Create a file at app/callback/route.ts:
app/callback/route.ts
import { handleAuth } from '@workos-inc/authkit-nextjs';

export const GET = handleAuth();
Make sure this route matches your NEXT_PUBLIC_WORKOS_REDIRECT_URI environment variable. If your redirect URI is http://localhost:3000/auth/callback, create the file at app/auth/callback/route.ts instead.

Customize the redirect path

By default, users are redirected to / after signing in. Customize this with the returnPathname option:
app/callback/route.ts
import { handleAuth } from '@workos-inc/authkit-nextjs';

export const GET = handleAuth({ 
  returnPathname: '/dashboard' 
});

Handle successful authentication

Use the onSuccess callback to save OAuth tokens or perform other actions:
app/callback/route.ts
import { handleAuth } from '@workos-inc/authkit-nextjs';

export const GET = handleAuth({
  onSuccess: async ({ user, oauthTokens, authenticationMethod }) => {
    // Save OAuth tokens from upstream providers
    if (oauthTokens) {
      await saveTokens(user.id, oauthTokens);
    }
    
    // Track how the user authenticated
    if (authenticationMethod) {
      await analytics.track('user_signed_in', {
        userId: user.id,
        method: authenticationMethod, // e.g., 'google-oauth', 'password'
      });
    }
  },
});

Step 2: Set up middleware

Middleware manages sessions and handles authentication for protected routes.
Create a proxy.ts file in your project root:
proxy.ts
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

export default authkitMiddleware();

// Protect specific routes
export const config = { 
  matcher: ['/dashboard/:path*', '/account'] 
};
Avoid using catch-all matcher patterns like ['/(.*)', '/:path*'] as they can intercept static assets (CSS, images, fonts) and break styling. If you need broad coverage, exclude Next.js internal paths:
export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Step 3: Wrap your app with AuthKitProvider

The AuthKitProvider enables client-side authentication hooks and handles auth edge cases. Update your root layout:
app/layout.tsx
import { AuthKitProvider } from '@workos-inc/authkit-nextjs/components';

export default function RootLayout({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  return (
    <html lang="en">
      <body>
        <AuthKitProvider>
          {children}
        </AuthKitProvider>
      </body>
    </html>
  );
}

Optimize with server-side data

Avoid an extra server action call by passing initial auth data:
app/layout.tsx
import { AuthKitProvider } from '@workos-inc/authkit-nextjs/components';
import { withAuth } from '@workos-inc/authkit-nextjs';

export default async function RootLayout({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  // Fetch auth data on the server
  const auth = await withAuth();
  
  // Remove accessToken (not needed on client)
  const { accessToken, ...initialAuth } = auth;
  
  return (
    <html lang="en">
      <body>
        <AuthKitProvider initialAuth={initialAuth}>
          {children}
        </AuthKitProvider>
      </body>
    </html>
  );
}

Step 4: Add authentication to your pages

Now you can access user data in both server and client components.

Server component example

Create a homepage with sign-in/sign-out functionality:
app/page.tsx
import Link from 'next/link';
import { 
  withAuth, 
  getSignInUrl, 
  getSignUpUrl,
  signOut 
} from '@workos-inc/authkit-nextjs';

export default async function HomePage() {
  const { user } = await withAuth();
  
  if (!user) {
    const signInUrl = await getSignInUrl();
    const signUpUrl = await getSignUpUrl();
    
    return (
      <div>
        <h1>Welcome to our app</h1>
        <Link href={signInUrl}>Sign in</Link>
        <Link href={signUpUrl}>Sign up</Link>
      </div>
    );
  }
  
  return (
    <div>
      <h1>Welcome back, {user.firstName}!</h1>
      <p>Email: {user.email}</p>
      
      <form action={async () => {
        'use server';
        await signOut();
      }}>
        <button type="submit">Sign out</button>
      </form>
    </div>
  );
}

Client component example

For client components, use the useAuth hook:
app/components/user-menu.tsx
'use client';

import { useAuth } from '@workos-inc/authkit-nextjs/components';

export function UserMenu() {
  const { user, loading } = useAuth();
  
  if (loading) {
    return <div>Loading...</div>;
  }
  
  if (!user) {
    return <div>Not signed in</div>;
  }
  
  return (
    <div>
      <img src={user.profilePictureUrl} alt={user.firstName} />
      <span>{user.firstName} {user.lastName}</span>
      <span>{user.email}</span>
    </div>
  );
}

Require authentication

Force users to sign in before accessing a page:
import { withAuth } from '@workos-inc/authkit-nextjs';

export default async function DashboardPage() {
  // Redirects to AuthKit if not signed in
  const { user } = await withAuth({ ensureSignedIn: true });
  
  return <div>Welcome to your dashboard, {user.firstName}!</div>;
}

Step 5: Test authentication

Start your development server and test the authentication flow:
1

Start your app

npm run dev
2

Navigate to your app

Open http://localhost:3000 in your browser
3

Click sign in

You’ll be redirected to the WorkOS AuthKit hosted authentication page
4

Authenticate

Sign in with your configured authentication method (email, Google, GitHub, etc.)
5

Return to your app

After successful authentication, you’ll be redirected back to your app with an active session

Optional: Add user impersonation UI

Display a banner when impersonating users for customer support:
app/layout.tsx
import { 
  AuthKitProvider, 
  Impersonation 
} from '@workos-inc/authkit-nextjs/components';

export default function RootLayout({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  return (
    <html lang="en">
      <body>
        <AuthKitProvider>
          <Impersonation />
          {children}
        </AuthKitProvider>
      </body>
    </html>
  );
}
The Impersonation component automatically shows a banner with user details and a “Stop impersonating” button when an admin is impersonating a user.

Next steps

Access tokens

Learn how to get access tokens for API calls to external services

Organization switching

Enable users to switch between different organizations

Feature flags

Use feature flags to control access to features for specific users

API key validation

Secure your API endpoints with WorkOS API keys

Common issues

Don’t wrap withAuth({ ensureSignedIn: true }) in try/catch. Next.js redirects must be called outside try/catch blocks. Use the return value to check for authentication instead:
const { user } = await withAuth();
if (!user) {
  // Handle unauthenticated state
}
This error occurs when importing server-side code in client components. Make sure you’re using useAuth() in client components and withAuth() in server components. Check that all files using withAuth() are server components (no 'use client' directive).
Catch-all middleware matchers can intercept CSS and image requests. Use a more specific matcher or exclude Next.js internal paths:
export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
Ensure your NEXT_PUBLIC_WORKOS_REDIRECT_URI exactly matches the callback route path and the redirect URI configured in your WorkOS dashboard. The URI must include the protocol (http:// or https://) and port number for local development.

Build docs developers (and LLMs) love