Quick and dirty no BS NextAuth + SQLite (Prisma) Setup in Next.js App Router

0x1c3c01d - Feb 16 - - Dev Community

This guide shows a basic approach for adding user authentication with NextAuth and a local SQLite database via Prisma in a Next.js 13+ App Router project.


1. Install Dependencies

npm install next-auth @next-auth/prisma-adapter @prisma/client
npx prisma init --datasource-provider sqlite
Enter fullscreen mode Exit fullscreen mode

This adds NextAuth, the official Prisma adapter for NextAuth, and Prisma Client. The second command sets up a starter prisma/schema.prisma.


2. Configure Prisma

In your new prisma/schema.prisma file, set up a local SQLite data source and include the required NextAuth models. Example:

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id            String          @id @default(cuid())
  name          String?
  email         String?         @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
  // Optional for WebAuthn support
  Authenticator Authenticator[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?
  access_token      String?
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?
  session_state     String?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model VerificationToken {
  identifier String
  token      String
  expires    DateTime

  @@unique([identifier, token])
}

// Optional for WebAuthn support
model Authenticator {
  credentialID         String  @unique
  userId               String
  providerAccountId    String
  credentialPublicKey  String
  counter              Int
  credentialDeviceType String
  credentialBackedUp   Boolean
  transports           String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@id([userId, credentialID])
}
Enter fullscreen mode Exit fullscreen mode

Then run:

npx prisma migrate dev --name init
npx prisma generate
Enter fullscreen mode Exit fullscreen mode

Add this to your .env file:

DATABASE_URL="file:./db.sqlite"
Enter fullscreen mode Exit fullscreen mode

3. Configure Session (App Router)

Create a file at utils/GetSession.ts:

import { NextAuthOptions, getServerSession } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient();

const authOptions: NextAuthOptions = {
    adapter: PrismaAdapter(prisma),
    providers: [
      GoogleProvider({
        clientId: process.env.GOOGLE_CLIENT_ID!,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      }),
    ],
    callbacks: {
      async session({ session, user }) {
        if (session.user) {
          session.user.id = user.id;
          session.user.role = user.role;
        }
        return session;
      },
    }
  };

/**
 * Helper function to get the session on the server without having to import the authOptions object every single time
 * @returns The session object or null
 */
const getSession = () => getServerSession(authOptions)

export { authOptions, getSession }
Enter fullscreen mode Exit fullscreen mode

This uses a Google OAuth provider. You can substitute any NextAuth provider(s) you like, or use CredentialsProvider for a custom username/password.

Make sure you have GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in your .env if you are using Google. For example:

GOOGLE_CLIENT_ID="123-abc.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="XYZ123"
Enter fullscreen mode Exit fullscreen mode

Create a file at api/auth/[...nextauth]/route.ts :

import { authOptions } from "@/utils/GetSession"
import NextAuth from "next-auth"

const handler = NextAuth(authOptions)

export { handler as GET, handler as POST }
Enter fullscreen mode Exit fullscreen mode

Create a component for the provider at components/auth/Provider.tsx :

"use client"
import type { Session } from "next-auth"
import { SessionProvider } from "next-auth/react"

export default function Providers({ session, children }: { session: Session | null, children: React.ReactNode }) {
    return (
        // @ts-expect-error - Known issue with Auth.js v5 + React 19 type definitions
        <SessionProvider session={session}>
            {children}
        </SessionProvider>
    )
}
Enter fullscreen mode Exit fullscreen mode

Finally, at app/layout.tsx:

import { getSession } from "@/utils/GetSession"
import Providers from "@/components/auth/Provider"

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

    return (
        <html lang="en">
            <body>
                <Providers session={session}>
                    {children}
                </Providers>
            </body>
        </html>
    )
}
Enter fullscreen mode Exit fullscreen mode

4. Using the Auth in the Client

NextAuth provides the useSession and signIn/signOut client helpers. Example:

'use client';
import { signIn, signOut, useSession } from 'next-auth/react';

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

  if (session) {
    return (
      <>
        Signed in as {session.user?.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    );
  }
  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

. .