Courses Curriculum Cities Blog Enroll Now
Build a SaaS with AI · Day 2 of 5 ~40 minutes

Day 2: Authentication and Usage-Gated AI Features

Add NextAuth authentication and build the core AI feature with usage tracking — free users get 10 generations per month, paid users get unlimited.

1
Day 1
2
Day 2
3
Day 3
4
Day 4
5
Day 5
What You'll Build

A working AI generation feature that authenticates users, checks their plan and credit usage before calling Claude, logs each generation to the database, and returns a 402 Payment Required when users exceed their limit.

1
Section 1 · 10 min

Configure NextAuth

Set up authentication with Google OAuth. Users will log in with their Google account — no password management required.

typescriptsrc/lib/auth.ts
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!
    })
  ],
  callbacks: {
    async session({ session, user }) {
      session.user.id = user.id
      session.user.plan = (user as any).plan
      session.user.creditsUsed = (user as any).creditsUsed
      session.user.creditsLimit = (user as any).creditsLimit
      return session
    }
  }
})
2
Section 2 · 12 min

The AI Generation API Route

The generation endpoint does four things: authenticate the user, check their credit limit, call Claude, and log the usage. All four must succeed or the request fails.

typescriptsrc/app/api/generate/route.ts
import { NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import Anthropic from '@anthropic-ai/sdk'

const claude = new Anthropic()

export async function POST(request: Request) {
  // 1. Authenticate
  const session = await auth()
  if (!session?.user?.id) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // 2. Check credits
  const user = await prisma.user.findUnique({
    where: { id: session.user.id },
    select: { plan: true, creditsUsed: true, creditsLimit: true }
  })

  if (user!.plan === 'FREE' && user!.creditsUsed >= user!.creditsLimit) {
    return NextResponse.json(
      { error: 'Credit limit reached. Upgrade to Pro for unlimited generations.', upgrade: true },
      { status: 402 }  // Payment Required
    )
  }

  // 3. Call Claude
  const { prompt } = await request.json()
  const response = await claude.messages.create({
    model: 'claude-opus-4-5', max_tokens: 1024,
    messages: [{ role: 'user', content: prompt }]
  })
  const result = response.content[0].text

  // 4. Log usage atomically
  await prisma.$transaction([
    prisma.generation.create({
      data: { userId: session.user.id, prompt, result, tokens: response.usage.output_tokens }
    }),
    prisma.user.update({
      where: { id: session.user.id },
      data: { creditsUsed: { increment: 1 } }
    })
  ])

  return NextResponse.json({ result })
}
3
Section 3 · 9 min

Dashboard: Show Usage and History

Show users how many credits they've used and their recent generations. This is the core dashboard of any AI SaaS.

typescriptsrc/app/dashboard/page.tsx
import { auth } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import { redirect } from 'next/navigation'

export default async function Dashboard() {
  const session = await auth()
  if (!session) redirect('/login')

  const [user, recentGenerations] = await Promise.all([
    prisma.user.findUnique({
      where: { id: session.user.id },
      select: { plan: true, creditsUsed: true, creditsLimit: true }
    }),
    prisma.generation.findMany({
      where: { userId: session.user.id },
      orderBy: { createdAt: 'desc' },
      take: 5
    })
  ])

  return (
    <div>
      <h1>Welcome, {session.user.name}</h1>
      <p>Plan: {user?.plan} | Credits: {user?.creditsUsed}/{user?.creditsLimit}</p>
    </div>
  )
}
4
Section 4 · 9 min

The Generate Page

The AI generation UI: a prompt input, submit button, and result display. Handles the 402 upgrade prompt gracefully.

typescriptsrc/app/dashboard/generate/page.tsx (client component)
'use client'
import { useState } from 'react'

export default function GeneratePage() {
  const [prompt, setPrompt] = useState('')
  const [result, setResult] = useState('')
  const [loading, setLoading] = useState(false)
  const [showUpgrade, setShowUpgrade] = useState(false)

  const generate = async () => {
    setLoading(true)
    const res = await fetch('/api/generate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ prompt })
    })
    const data = await res.json()
    if (res.status === 402) { setShowUpgrade(true) }
    else { setResult(data.result) }
    setLoading(false)
  }

  return (
    <div>
      {showUpgrade && <div className="upgrade-banner">You've used all your free credits. <a href="/pricing">Upgrade to Pro</a></div>}
      <textarea value={prompt} onChange={e => setPrompt(e.target.value)} />
      <button onClick={generate} disabled={loading}>{loading ? 'Generating...' : 'Generate'}</button>
      {result && <div className="result">{result}</div>}
    </div>
  )
}

What You Learned Today

  • Configured NextAuth with Google OAuth and added plan/credits to the session object
  • Built a generation API route that authenticates, checks credits, calls Claude, and logs usage in a transaction
  • Displayed real-time usage data and generation history from the database on the dashboard
  • Handled the 402 Payment Required response by showing an upgrade prompt in the UI
Your Challenge

Go Further on Your Own

  • Add a rate limiter using Upstash Redis to prevent a single user from abusing the API within a short window
  • Create a /api/usage endpoint that returns the user's current month usage stats as JSON
  • Add email verification so users must confirm their email before getting any free credits
Day 2 Complete

Nice work. Keep going.

Day 3 is ready when you are.

Continue to Day 3
Course Progress
40%

Want live instruction and hands-on projects? Join the AI bootcamp — 3 days, 5 cities.

Finished this lesson?