[{"data":1,"prerenderedAt":541},["ShallowReactive",2],{"blog-how-to/nextauth-setup":3},{"id":4,"title":5,"body":6,"category":522,"date":523,"dateModified":523,"description":524,"draft":525,"extension":526,"faq":527,"featured":525,"headerVariant":528,"image":527,"keywords":527,"meta":529,"navigation":530,"ogDescription":531,"ogTitle":527,"path":532,"readTime":527,"schemaOrg":533,"schemaType":534,"seo":535,"sitemap":536,"stem":537,"tags":538,"twitterCard":539,"__hash__":540},"blog/blog/how-to/nextauth-setup.md","How to Set Up NextAuth.js Securely",{"type":7,"value":8,"toc":503},"minimark",[9,13,17,21,27,30,46,51,54,58,78,113,142,162,180,199,215,228,258,271,327,331,370,374,379,385,389,395,399,409,413,419,423,430,434,437,465,484],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,5],"h1",{"id":16},"how-to-set-up-nextauthjs-securely",[18,19,20],"p",{},"Production-ready authentication for Next.js App Router",[22,23,24],"tldr",{},[18,25,26],{},"TL;DR (25 minutes):\nInstall next-auth with a database adapter (Prisma recommended). Generate a strong NEXTAUTH_SECRET with openssl. Configure OAuth providers with exact redirect URIs. Use middleware.ts to protect routes. Always verify sessions server-side on API routes. Check authorization (not just authentication) before accessing resources.",[18,28,29],{},"Prerequisites:",[31,32,33,37,40,43],"ul",{},[34,35,36],"li",{},"Next.js 13+ with App Router",[34,38,39],{},"Node.js 18 or later",[34,41,42],{},"Database (PostgreSQL, MySQL, or MongoDB)",[34,44,45],{},"OAuth provider credentials (Google, GitHub, etc.)",[47,48,50],"h2",{"id":49},"why-this-matters","Why This Matters",[18,52,53],{},"NextAuth.js (now Auth.js) is the most popular authentication library for Next.js, but misconfiguration leads to serious vulnerabilities. Missing NEXTAUTH_SECRET, improper session handling, and forgetting to protect API routes are common mistakes that expose user data.",[47,55,57],{"id":56},"step-by-step-guide","Step-by-Step Guide",[59,60,62,67],"step",{"number":61},"1",[63,64,66],"h3",{"id":65},"install-nextauthjs-and-dependencies","Install NextAuth.js and dependencies",[68,69,74],"pre",{"className":70,"code":72,"language":73},[71],"language-text","# Core package\nnpm install next-auth\n\n# Database adapter (choose one)\nnpm install @next-auth/prisma-adapter @prisma/client prisma\n# or\nnpm install @auth/drizzle-adapter drizzle-orm\n","text",[75,76,72],"code",{"__ignoreMap":77},"",[59,79,81,85,88,94,101,107],{"number":80},"2",[63,82,84],{"id":83},"generate-and-configure-environment-variables","Generate and configure environment variables",[18,86,87],{},"Create a strong secret and configure your environment:",[68,89,92],{"className":90,"code":91,"language":73},[71],"# Generate a secure secret (run in terminal)\nopenssl rand -base64 32\n",[75,93,91],{"__ignoreMap":77},[18,95,96,97,100],{},"Add to your ",[75,98,99],{},".env.local",":",[68,102,105],{"className":103,"code":104,"language":73},[71],"# REQUIRED: Authentication secret\n# Generate with: openssl rand -base64 32\nNEXTAUTH_SECRET=your-generated-secret-here\n\n# REQUIRED in production: Your app URL\nNEXTAUTH_URL=https://yourapp.com\n\n# Database\nDATABASE_URL=\"postgresql://user:password@localhost:5432/myapp\"\n\n# OAuth Providers\nGOOGLE_CLIENT_ID=your-google-client-id\nGOOGLE_CLIENT_SECRET=your-google-client-secret\nGITHUB_CLIENT_ID=your-github-client-id\nGITHUB_CLIENT_SECRET=your-github-client-secret\n",[75,106,104],{"__ignoreMap":77},[108,109,110],"warning-box",{},[18,111,112],{},"Critical:\nNEXTAUTH_SECRET must be set in production. Without it, sessions are signed with a default key and are not secure. Never commit secrets to git.",[59,114,116,120,127,133,136],{"number":115},"3",[63,117,119],{"id":118},"set-up-the-database-schema","Set up the database schema",[18,121,122,123,126],{},"Add NextAuth tables to your Prisma schema (",[75,124,125],{},"prisma/schema.prisma","):",[68,128,131],{"className":129,"code":130,"language":73},[71],"datasource db {\n  provider = \"postgresql\"\n  url      = env(\"DATABASE_URL\")\n}\n\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n\nmodel Account {\n  id                String  @id @default(cuid())\n  userId            String\n  type              String\n  provider          String\n  providerAccountId String\n  refresh_token     String? @db.Text\n  access_token      String? @db.Text\n  expires_at        Int?\n  token_type        String?\n  scope             String?\n  id_token          String? @db.Text\n  session_state     String?\n\n  user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([provider, providerAccountId])\n}\n\nmodel Session {\n  id           String   @id @default(cuid())\n  sessionToken String   @unique\n  userId       String\n  expires      DateTime\n  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel User {\n  id            String    @id @default(cuid())\n  name          String?\n  email         String?   @unique\n  emailVerified DateTime?\n  image         String?\n  role          String    @default(\"user\")\n  accounts      Account[]\n  sessions      Session[]\n}\n\nmodel VerificationToken {\n  identifier String\n  token      String   @unique\n  expires    DateTime\n\n  @@unique([identifier, token])\n}\n",[75,132,130],{"__ignoreMap":77},[18,134,135],{},"Run the migration:",[68,137,140],{"className":138,"code":139,"language":73},[71],"npx prisma migrate dev --name add-auth-tables\nnpx prisma generate\n",[75,141,139],{"__ignoreMap":77},[59,143,145,149,156],{"number":144},"4",[63,146,148],{"id":147},"create-the-nextauth-configuration","Create the NextAuth configuration",[18,150,151,152,155],{},"Create ",[75,153,154],{},"lib/auth.ts"," for your auth configuration:",[68,157,160],{"className":158,"code":159,"language":73},[71],"import { NextAuthOptions } from 'next-auth';\nimport { PrismaAdapter } from '@next-auth/prisma-adapter';\nimport GoogleProvider from 'next-auth/providers/google';\nimport GitHubProvider from 'next-auth/providers/github';\nimport { prisma } from '@/lib/prisma';\n\nexport const authOptions: NextAuthOptions = {\n  adapter: PrismaAdapter(prisma),\n\n  providers: [\n    GoogleProvider({\n      clientId: process.env.GOOGLE_CLIENT_ID!,\n      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n      // Request offline access for refresh tokens\n      authorization: {\n        params: {\n          prompt: 'consent',\n          access_type: 'offline',\n          response_type: 'code'\n        }\n      }\n    }),\n    GitHubProvider({\n      clientId: process.env.GITHUB_CLIENT_ID!,\n      clientSecret: process.env.GITHUB_CLIENT_SECRET!,\n    }),\n  ],\n\n  session: {\n    strategy: 'database', // Use database sessions (more secure)\n    maxAge: 30 * 24 * 60 * 60, // 30 days\n    updateAge: 24 * 60 * 60, // Update session every 24 hours\n  },\n\n  callbacks: {\n    async session({ session, user }) {\n      // Add user ID and role to the session\n      if (session.user) {\n        session.user.id = user.id;\n        session.user.role = user.role;\n      }\n      return session;\n    },\n    async signIn({ user, account, profile }) {\n      // Optional: Add custom sign-in logic\n      // Return false to deny access\n      return true;\n    },\n  },\n\n  pages: {\n    signIn: '/auth/signin',\n    error: '/auth/error',\n  },\n\n  // Security options\n  cookies: {\n    sessionToken: {\n      name: process.env.NODE_ENV === 'production'\n        ? '__Secure-next-auth.session-token'\n        : 'next-auth.session-token',\n      options: {\n        httpOnly: true,\n        sameSite: 'lax',\n        path: '/',\n        secure: process.env.NODE_ENV === 'production',\n      },\n    },\n  },\n\n  debug: process.env.NODE_ENV === 'development',\n};\n\n// Type augmentation for session\ndeclare module 'next-auth' {\n  interface Session {\n    user: {\n      id: string;\n      role: string;\n      name?: string | null;\n      email?: string | null;\n      image?: string | null;\n    };\n  }\n  interface User {\n    role: string;\n  }\n}\n",[75,161,159],{"__ignoreMap":77},[59,163,165,169,174],{"number":164},"5",[63,166,168],{"id":167},"create-the-api-route-handler","Create the API route handler",[18,170,151,171,100],{},[75,172,173],{},"app/api/auth/[...nextauth]/route.ts",[68,175,178],{"className":176,"code":177,"language":73},[71],"import NextAuth from 'next-auth';\nimport { authOptions } from '@/lib/auth';\n\nconst handler = NextAuth(authOptions);\n\nexport { handler as GET, handler as POST };\n",[75,179,177],{"__ignoreMap":77},[59,181,183,187,193],{"number":182},"6",[63,184,186],{"id":185},"protect-routes-with-middleware","Protect routes with middleware",[18,188,151,189,192],{},[75,190,191],{},"middleware.ts"," in your project root:",[68,194,197],{"className":195,"code":196,"language":73},[71],"import { withAuth } from 'next-auth/middleware';\nimport { NextResponse } from 'next/server';\n\nexport default withAuth(\n  function middleware(req) {\n    const token = req.nextauth.token;\n    const path = req.nextUrl.pathname;\n\n    // Admin routes require admin role\n    if (path.startsWith('/admin') && token?.role !== 'admin') {\n      return NextResponse.redirect(new URL('/unauthorized', req.url));\n    }\n\n    return NextResponse.next();\n  },\n  {\n    callbacks: {\n      authorized: ({ token }) => !!token,\n    },\n  }\n);\n\nexport const config = {\n  matcher: [\n    '/dashboard/:path*',\n    '/settings/:path*',\n    '/admin/:path*',\n    '/api/protected/:path*',\n  ],\n};\n",[75,198,196],{"__ignoreMap":77},[59,200,202,206,209],{"number":201},"7",[63,203,205],{"id":204},"protect-api-routes-with-session-checks","Protect API routes with session checks",[18,207,208],{},"Always verify sessions in API routes:",[68,210,213],{"className":211,"code":212,"language":73},[71],"import { getServerSession } from 'next-auth';\nimport { authOptions } from '@/lib/auth';\nimport { NextResponse } from 'next/server';\nimport { prisma } from '@/lib/prisma';\n\n// GET /api/posts - Get user's posts\nexport async function GET(request: Request) {\n  const session = await getServerSession(authOptions);\n\n  if (!session) {\n    return NextResponse.json(\n      { error: 'Unauthorized' },\n      { status: 401 }\n    );\n  }\n\n  // IMPORTANT: Scope data to the authenticated user\n  const posts = await prisma.post.findMany({\n    where: { authorId: session.user.id },\n    orderBy: { createdAt: 'desc' },\n  });\n\n  return NextResponse.json(posts);\n}\n\n// DELETE /api/posts/[id] - Delete a post\nexport async function DELETE(\n  request: Request,\n  { params }: { params: { id: string } }\n) {\n  const session = await getServerSession(authOptions);\n\n  if (!session) {\n    return NextResponse.json(\n      { error: 'Unauthorized' },\n      { status: 401 }\n    );\n  }\n\n  // Check authorization: Does this user own this resource?\n  const post = await prisma.post.findUnique({\n    where: { id: params.id },\n  });\n\n  if (!post) {\n    return NextResponse.json(\n      { error: 'Post not found' },\n      { status: 404 }\n    );\n  }\n\n  if (post.authorId !== session.user.id && session.user.role !== 'admin') {\n    return NextResponse.json(\n      { error: 'Forbidden' },\n      { status: 403 }\n    );\n  }\n\n  await prisma.post.delete({\n    where: { id: params.id },\n  });\n\n  return NextResponse.json({ success: true });\n}\n",[75,214,212],{"__ignoreMap":77},[59,216,218,222],{"number":217},"8",[63,219,221],{"id":220},"get-session-in-server-components","Get session in Server Components",[68,223,226],{"className":224,"code":225,"language":73},[71],"import { getServerSession } from 'next-auth';\nimport { authOptions } from '@/lib/auth';\nimport { redirect } from 'next/navigation';\n\nexport default async function DashboardPage() {\n  const session = await getServerSession(authOptions);\n\n  if (!session) {\n    redirect('/auth/signin');\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>Welcome, {session.user.name}\u003C/h1>\n      \u003Cp>Role: {session.user.role}\u003C/p>\n    \u003C/div>\n  );\n}\n",[75,227,225],{"__ignoreMap":77},[59,229,231,235,240,246,252],{"number":230},"9",[63,232,234],{"id":233},"set-up-sessionprovider-for-client-components","Set up SessionProvider for client components",[18,236,151,237,100],{},[75,238,239],{},"app/providers.tsx",[68,241,244],{"className":242,"code":243,"language":73},[71],"'use client';\n\nimport { SessionProvider } from 'next-auth/react';\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n  return \u003CSessionProvider>{children}\u003C/SessionProvider>;\n}\n",[75,245,243],{"__ignoreMap":77},[18,247,248,249,100],{},"Wrap your app in ",[75,250,251],{},"app/layout.tsx",[68,253,256],{"className":254,"code":255,"language":73},[71],"import { Providers } from './providers';\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody>\n        \u003CProviders>{children}\u003C/Providers>\n      \u003C/body>\n    \u003C/html>\n  );\n}\n",[75,257,255],{"__ignoreMap":77},[59,259,261,265],{"number":260},"10",[63,262,264],{"id":263},"use-session-in-client-components","Use session in client components",[68,266,269],{"className":267,"code":268,"language":73},[71],"'use client';\n\nimport { useSession, signIn, signOut } from 'next-auth/react';\n\nexport function AuthButton() {\n  const { data: session, status } = useSession();\n\n  if (status === 'loading') {\n    return \u003Cdiv>Loading...\u003C/div>;\n  }\n\n  if (session) {\n    return (\n      \u003Cdiv>\n        \u003Cp>Signed in as {session.user?.email}\u003C/p>\n        \u003Cbutton onClick={() => signOut()}>Sign out\u003C/button>\n      \u003C/div>\n    );\n  }\n\n  return \u003Cbutton onClick={() => signIn()}>Sign in\u003C/button>;\n}\n",[75,270,268],{"__ignoreMap":77},[108,272,273,276],{},[18,274,275],{},"NextAuth.js Security Checklist:",[31,277,278,285,291,297,303,309,315,321],{},[34,279,280,284],{},[281,282,283],"strong",{},"NEXTAUTH_SECRET set in production"," - Use openssl rand -base64 32",[34,286,287,290],{},[281,288,289],{},"Database adapter configured"," - JWT-only sessions can't be revoked",[34,292,293,296],{},[281,294,295],{},"Exact redirect URIs"," - No wildcards in OAuth provider settings",[34,298,299,302],{},[281,300,301],{},"Session checks on API routes"," - Don't rely only on middleware",[34,304,305,308],{},[281,306,307],{},"Authorization checks"," - Verify users can access specific resources",[34,310,311,314],{},[281,312,313],{},"Secure cookies enabled"," - httpOnly, secure, sameSite set properly",[34,316,317,320],{},[281,318,319],{},"Environment variables protected"," - Never commit secrets to git",[34,322,323,326],{},[281,324,325],{},"CSRF protection enabled"," - NextAuth handles this by default",[47,328,330],{"id":329},"how-to-verify-it-worked","How to Verify It Worked",[332,333,334,340,346,352,358,364],"ol",{},[34,335,336,339],{},[281,337,338],{},"Test unauthenticated access:"," Visit /dashboard without signing in - should redirect to sign-in",[34,341,342,345],{},[281,343,344],{},"Test API protection:"," Call /api/protected without a session - should return 401",[34,347,348,351],{},[281,349,350],{},"Test authorization:"," Try to access another user's data - should return 403",[34,353,354,357],{},[281,355,356],{},"Inspect cookies:"," Check that session cookie has httpOnly and secure flags",[34,359,360,363],{},[281,361,362],{},"Test sign out:"," Sign out and verify old session is invalidated",[34,365,366,369],{},[281,367,368],{},"Check database:"," Verify sessions are stored in your database",[47,371,373],{"id":372},"common-errors-troubleshooting","Common Errors & Troubleshooting",[375,376,378],"h4",{"id":377},"error-nextauth_secret-missing","Error: \"NEXTAUTH_SECRET missing\"",[18,380,381,382],{},"Set NEXTAUTH_SECRET in your environment. In production, this is required. Generate with: ",[75,383,384],{},"openssl rand -base64 32",[375,386,388],{"id":387},"error-redirect_uri_mismatch","Error: \"redirect_uri_mismatch\"",[18,390,391,392],{},"The callback URL doesn't match your OAuth provider settings. Add the exact URL including protocol and path: ",[75,393,394],{},"https://yourapp.com/api/auth/callback/google",[375,396,398],{"id":397},"session-is-null-in-api-route","Session is null in API route",[18,400,401,402,405,406],{},"Make sure you're passing authOptions to getServerSession: ",[75,403,404],{},"getServerSession(authOptions)",", not just ",[75,407,408],{},"getServerSession()",[375,410,412],{"id":411},"user-id-not-in-session","User ID not in session",[18,414,415,416],{},"Add the session callback to include user.id. With database sessions, use ",[75,417,418],{},"session.user.id = user.id",[375,420,422],{"id":421},"prisma-adapter-errors","Prisma adapter errors",[18,424,425,426,429],{},"Run ",[75,427,428],{},"npx prisma generate"," after schema changes. Ensure DATABASE_URL is set correctly.",[375,431,433],{"id":432},"cookies-not-being-set","Cookies not being set",[18,435,436],{},"In production, cookies require HTTPS. Set secure: true only when NODE_ENV is production.",[438,439,440,447,453,459],"faq-section",{},[441,442,444],"faq-item",{"question":443},"Should I use JWT or database sessions?",[18,445,446],{},"Database sessions are more secure because you can revoke them immediately. JWTs are stateless and faster but can't be revoked until they expire. Use database sessions unless you have a specific need for JWTs (like a mobile app or cross-domain auth).",[441,448,450],{"question":449},"How do I add credentials/password login?",[18,451,452],{},"Use CredentialsProvider, but be aware it doesn't work with database sessions by default. You'll need JWT sessions or implement your own session management. OAuth is generally more secure and easier to implement correctly.",[441,454,456],{"question":455},"How do I protect specific pages based on role?",[18,457,458],{},"Add role to your User model and session callback. Then check session.user.role in middleware or your page components. Reject access with a redirect or 403 response.",[441,460,462],{"question":461},"Why is getServerSession() returning null?",[18,463,464],{},"Common causes: 1) Not passing authOptions, 2) Calling from a client component (use useSession instead), 3) Session cookie not being sent (check NEXTAUTH_URL matches your domain).",[18,466,467,470,475,476,475,480],{},[281,468,469],{},"Related guides:",[471,472,474],"a",{"href":473},"/blog/how-to/oauth-setup","OAuth Setup"," ·\n",[471,477,479],{"href":478},"/blog/how-to/session-management","Session Management",[471,481,483],{"href":482},"/blog/how-to/add-authentication-nextjs","Add Authentication to Next.js",[485,486,487,493,498],"related-articles",{},[488,489],"related-card",{"description":490,"href":491,"title":492},"Step-by-step guide to implementing secure database backups. Automated backups, encryption, retention policies, and disas","/blog/how-to/database-backups","How to Set Up Secure Database Backups",[488,494],{"description":495,"href":496,"title":497},"Step-by-step guide to database encryption. Implement encryption at rest, in transit, and application-level encryption fo","/blog/how-to/database-encryption","How to Encrypt Database Data",[488,499],{"description":500,"href":501,"title":502},"Complete guide to setting up .env files for local development. Learn the dotenv package, file naming conventions, and ho","/blog/how-to/dotenv-setup","How to Set Up .env Files - Complete Guide",{"title":77,"searchDepth":504,"depth":504,"links":505},2,[506,507,520,521],{"id":49,"depth":504,"text":50},{"id":56,"depth":504,"text":57,"children":508},[509,511,512,513,514,515,516,517,518,519],{"id":65,"depth":510,"text":66},3,{"id":83,"depth":510,"text":84},{"id":118,"depth":510,"text":119},{"id":147,"depth":510,"text":148},{"id":167,"depth":510,"text":168},{"id":185,"depth":510,"text":186},{"id":204,"depth":510,"text":205},{"id":220,"depth":510,"text":221},{"id":233,"depth":510,"text":234},{"id":263,"depth":510,"text":264},{"id":329,"depth":504,"text":330},{"id":372,"depth":504,"text":373},"how-to","2026-01-20","Complete guide to secure NextAuth.js setup. Configure providers, protect API routes, secure sessions with database adapters, and implement middleware protection.",false,"md",null,"yellow",{},true,"Production-ready NextAuth.js configuration with security best practices.","/blog/how-to/nextauth-setup","[object Object]","HowTo",{"title":5,"description":524},{"loc":532},"blog/how-to/nextauth-setup",[],"summary_large_image","iNC-xaFxC5ZKndFLTLawVesAWMCOwl8vZD4RFn2zJPU",1775843928117]