AI Codex
Developer PathStep 17 of 20
← Prev·Next →
Infrastructure & DeploymentHow It Works

Adding authentication to your Claude app with NextAuth

In brief

Protect your API routes, tie conversations to users, and track per-user costs. The full integration from NextAuth setup to authenticated Claude calls.

9 min read·

Contents

Sign in to save

Without authentication, anyone who finds your Claude app can use your API key. Adding auth is not optional once you move past localhost — it protects your budget, enables per-user features, and is the foundation for conversation history, rate limiting, and billing.

This guide covers: NextAuth setup, protecting your API routes, passing user identity to Supabase, and tying Claude conversations to authenticated users.

1. Install and configure NextAuth

npm install next-auth @auth/supabase-adapter

Create app/api/auth/[...nextauth]/route.ts:

import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import { SupabaseAdapter } from '@auth/supabase-adapter'

const handler = NextAuth({
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  adapter: SupabaseAdapter({
    url: process.env.NEXT_PUBLIC_SUPABASE_URL!,
    secret: process.env.SUPABASE_SERVICE_ROLE_KEY!,
  }),
  callbacks: {
    session({ session, user }) {
      // Add user ID to session so we can use it in API routes
      session.user.id = user.id
      return session
    },
  },
})

export { handler as GET, handler as POST }

Add to .env.local:

GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
NEXTAUTH_SECRET=generate_with_openssl_rand_base64_32
NEXTAUTH_URL=http://localhost:3000

2. Extend the session type

NextAuth's default session type does not include id. Extend it:

// types/next-auth.d.ts
import 'next-auth'

declare module 'next-auth' {
  interface Session {
    user: {
      id: string
      name?: string | null
      email?: string | null
      image?: string | null
    }
  }
}

3. Protect your Claude API route

// app/api/chat/route.ts
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import Anthropic from '@anthropic-ai/sdk'

export async function POST(request: Request) {
  // Check authentication first
  const session = await getServerSession(authOptions)
  if (!session?.user?.id) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { messages } = await request.json()
  const userId = session.user.id

  const anthropic = new Anthropic()

  try {
    const response = await anthropic.messages.create({
      model: 'claude-opus-4-5',
      max_tokens: 1024,
      messages,
    })

    return Response.json({
      content: response.content[0],
      usage: response.usage,
    })
  } catch (err) {
    console.error('Claude API error:', err)
    return Response.json({ error: 'Claude call failed' }, { status: 500 })
  }
}

4. Middleware to protect entire route groups

Rather than checking auth in every route handler, use Next.js middleware:

// middleware.ts
import { withAuth } from 'next-auth/middleware'

export default withAuth({
  callbacks: {
    authorized({ token }) {
      return !!token
    },
  },
})

export const config = {
  matcher: [
    '/chat/:path*',
    '/api/chat/:path*',
    '/dashboard/:path*',
  ],
}

This redirects unauthenticated users to the sign-in page automatically.

5. Accessing user ID in client components

// components/Chat.tsx
'use client'
import { useSession } from 'next-auth/react'

export default function Chat() {
  const { data: session, status } = useSession()

  if (status === 'loading') return <p>Loading...</p>
  if (!session) return <p>Please sign in</p>

  return (
    <div>
      <p>Welcome, {session.user.name}</p>
      {/* chat UI */}
    </div>
  )
}

6. Wrap your app with the session provider

// app/layout.tsx
import { getServerSession } from 'next-auth'
import { SessionProvider } from '@/components/SessionProvider'
import { authOptions } from '@/lib/auth'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const session = await getServerSession(authOptions)

  return (
    <html>
      <body>
        <SessionProvider session={session}>
          {children}
        </SessionProvider>
      </body>
    </html>
  )
}
// components/SessionProvider.tsx
'use client'
import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react'
import type { Session } from 'next-auth'

export function SessionProvider({ session, children }: { session: Session | null; children: React.ReactNode }) {
  return <NextAuthSessionProvider session={session}>{children}</NextAuthSessionProvider>
}

7. Sign in / sign out UI

// components/AuthButton.tsx
'use client'
import { signIn, signOut, useSession } from 'next-auth/react'

export default function AuthButton() {
  const { data: session } = useSession()

  if (session) {
    return (
      <button onClick={() => signOut()}>
        Sign out ({session.user?.email})
      </button>
    )
  }

  return (
    <button onClick={() => signIn('google')}>
      Sign in with Google
    </button>
  )
}

What comes after auth

Once you have user IDs flowing, you can build:

  • Per-user conversation history — store messages in Supabase with user_id foreign key
  • Per-user rate limiting — track usage by user_id (see the rate limiting guide)
  • Cost attribution — know exactly how many tokens each user is consuming
  • Billing — gate features by plan, track usage against billing periods

Authentication is infrastructure. It feels like overhead until you need to refund a user's bill, debug a specific user's conversation, or block an abusive account. Do it early.

Further reading

  • Claude API quickstart — the fastest path from zero to a working Claude API integration
  • Available models — current Claude model IDs and context window sizes for your API calls
  • Prompt caching — reducing API costs on repeated calls by caching system prompts and long conversation histories

Related tools

Next in Developer Path · Step 18 of 20

Continue to the next article in the learning path

Next article →

Weekly brief

For people actually using Claude at work.

Each week: one thing Claude can do in your work that most people haven't figured out yet — plus the failure modes to avoid. No tutorials. No hype.

No spam. Unsubscribe anytime.

What to read next

All articles →