👋
Welcome to my blog!

Setup Authentication in NextJS App Router Using Next-Auth and Prisma

Signin with google with new NextJS App Router using Next-Auth and Prisma

Setup Authentication in NextJS App Router Using Next-Auth and Prisma
React
NextJS
Prisma
NextAuth
Authentication

Published At

6/12/2023

Reading Time

~ 7 min read

Add next-auth packages

bash
pnpm add @auth/prisma-adapter next-auth
bash
pnpm add @auth/prisma-adapter next-auth

Setup Prisma and add the auth tables

schema.prisma
prisma
generator client {
  provider = "prisma-client-js"
}
 
datasource db {
  provider          = "postgresql"
  url               = env("POSTGRES_PRISMA_URL")
  directUrl         = env("POSTGRES_URL")
  shadowDatabaseUrl = env("POSTGRES_SHADOW_URL")
}
 
model Account {
  id                String  @id @default(uuid())
  userId            String  @unique
  type              String  
  provider          String  
  providerAccountId String  
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?    
  token_type        String? 
  scope             String? 
  id_token          String? 
  session_state     String? 
 
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
 
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
 
  @@unique([provider, providerAccountId])
}
 
model Session {
  id           String   @id @default(uuid())
  sessionToken String   @unique
  userId       String   
  expires      DateTime 
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}
 
model User {
  id            String    @id @default(uuid())
  email         String    @unique
  name          String?   
  emailVerified DateTime? 
  image         String?   
 
  accounts Account[]
  sessions Session[]
 
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
 
  @@map("users")
}
 
model VerificationToken {
  identifier String   
  token      String   @unique
  expires    DateTime 
 
  @@unique([identifier, token])
}
schema.prisma
prisma
generator client {
  provider = "prisma-client-js"
}
 
datasource db {
  provider          = "postgresql"
  url               = env("POSTGRES_PRISMA_URL")
  directUrl         = env("POSTGRES_URL")
  shadowDatabaseUrl = env("POSTGRES_SHADOW_URL")
}
 
model Account {
  id                String  @id @default(uuid())
  userId            String  @unique
  type              String  
  provider          String  
  providerAccountId String  
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?    
  token_type        String? 
  scope             String? 
  id_token          String? 
  session_state     String? 
 
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
 
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
 
  @@unique([provider, providerAccountId])
}
 
model Session {
  id           String   @id @default(uuid())
  sessionToken String   @unique
  userId       String   
  expires      DateTime 
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}
 
model User {
  id            String    @id @default(uuid())
  email         String    @unique
  name          String?   
  emailVerified DateTime? 
  image         String?   
 
  accounts Account[]
  sessions Session[]
 
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
 
  @@map("users")
}
 
model VerificationToken {
  identifier String   
  token      String   @unique
  expires    DateTime 
 
  @@unique([identifier, token])
}

Create file

src/lib/auth.ts
ts
import type { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
 
export const authOptions: NextAuthOptions = {
  // @ts-ignore
  adapter: PrismaAdapter(prisma),
  session: {
    strategy: "jwt",
  },
  pages: {
    signIn: "/",
    signOut: "/",
  },
  providers: [
    GoogleProvider({
      clientId: "*****",
      clientSecret: "****",
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code",
        },
      },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      console.log("fire signin Callback");
      return true;
    },
    async redirect({ url, baseUrl }) {
      console.log("fire redirect Callback");
      return baseUrl;
    },
    async session({ session, user, token }) {
      console.log("fire SESSION Callback");
      return {
        ...session,
        user: {
          ...session.user,
          id: token.id,
          randomKey: token.randomKey,
        },
      };
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      console.log("fire jwt Callback");
      if (user) {
        const u = user as unknown as any;
        return {
          ...token,
          id: u.id,
          randomKey: u.randomKey,
        };
      }
      return token;
    },
  },
};
 
src/lib/auth.ts
ts
import type { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
 
export const authOptions: NextAuthOptions = {
  // @ts-ignore
  adapter: PrismaAdapter(prisma),
  session: {
    strategy: "jwt",
  },
  pages: {
    signIn: "/",
    signOut: "/",
  },
  providers: [
    GoogleProvider({
      clientId: "*****",
      clientSecret: "****",
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code",
        },
      },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      console.log("fire signin Callback");
      return true;
    },
    async redirect({ url, baseUrl }) {
      console.log("fire redirect Callback");
      return baseUrl;
    },
    async session({ session, user, token }) {
      console.log("fire SESSION Callback");
      return {
        ...session,
        user: {
          ...session.user,
          id: token.id,
          randomKey: token.randomKey,
        },
      };
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      console.log("fire jwt Callback");
      if (user) {
        const u = user as unknown as any;
        return {
          ...token,
          id: u.id,
          randomKey: u.randomKey,
        };
      }
      return token;
    },
  },
};
 

Now create a NextAuth route

src/app/api/auth/[...nextauth]/route.ts
ts
import NextAuth from "next-auth";
import { authOptions } from "@/lib/auth";
 
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
src/app/api/auth/[...nextauth]/route.ts
ts
import NextAuth from "next-auth";
import { authOptions } from "@/lib/auth";
 
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

Create Login and Logout buttons

src/components/button.ts
tsx
"use client";
 
import { signIn, signOut } from "next-auth/react";
 
export const LoginButton = () => {
  return <button onClick={() => signIn("google")}>Login</button>;
};
 
export const LogoutButton = () => {
  return <button onClick={() => signOut()}>Logout</button>;
};
src/components/button.ts
tsx
"use client";
 
import { signIn, signOut } from "next-auth/react";
 
export const LoginButton = () => {
  return <button onClick={() => signIn("google")}>Login</button>;
};
 
export const LogoutButton = () => {
  return <button onClick={() => signOut()}>Logout</button>;
};

Now do the following configurations

src/app/page.tsx
tsx
import { prisma } from "@/lib/prisma";
import { LoginButton, LogoutButton } from "@/components/buttons";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
 
async function getPosts() {
  return await prisma.user.findMany();
}
 
export default async function Home() {
  const session = await getServerSession(authOptions);
  const posts = await getPosts();
  return (
    <>
      <code>
        <pre className="">{JSON.stringify(posts, undefined, 2)}</pre>
        <pre className="">{JSON.stringify(session, undefined, 2)}</pre>
      </code>
      <div>
        <LoginButton />
        <LogoutButton />
      </div>
    </>
  );
}
src/app/page.tsx
tsx
import { prisma } from "@/lib/prisma";
import { LoginButton, LogoutButton } from "@/components/buttons";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
 
async function getPosts() {
  return await prisma.user.findMany();
}
 
export default async function Home() {
  const session = await getServerSession(authOptions);
  const posts = await getPosts();
  return (
    <>
      <code>
        <pre className="">{JSON.stringify(posts, undefined, 2)}</pre>
        <pre className="">{JSON.stringify(session, undefined, 2)}</pre>
      </code>
      <div>
        <LoginButton />
        <LogoutButton />
      </div>
    </>
  );
}

as I'm using Google as a login, you need to get Access Keys from the provider. More details related to providers can be found at:

Do you have any questions, or simply wish to contact me privately? Don't hesitate to shoot me a DM on Twitter.

Have a wonderful day.
Abhishek 🙏

Join My Exclusive Newsletter Community

Step into a world where creativity intersects with technology. By subscribing, you'll get a front-row seat to my latest musings, full-stack development resources, and exclusive previews of future posts. Each email is a crafted experience that includes:

  • In-depth looks at my covert projects and musings to ignite your imagination.
  • Handpicked frontend development resources and current explorations, aimed at expanding your developer toolkit.
  • A monthly infusion of inspiration with my personal selection of quotes, books, and music.

Embrace the confluence of words and wonder, curated thoughtfully and sent straight to your inbox.

No fluff. Just the highest caliber of ideas.