In this guide, we walk through integrating Auth.js with both Credentials and Google OAuth providers, using Prisma as the database ORM. We'll also dockerize the entire setup for easy deployment. From configuring environment variables to securing authentication flows and syncing your Prisma schema, this setup gives you a robust and scalable authentication system ready for production.
Create a next js application with npx create-next-app@latest authjs-with-prisma now go to that folder cd /auth-js-prisma.
Install required packages. npm install prisma next-auth@beta @prisma/client @auth/prisma-adapter bcryptjs.
Now create auth-secret with this command npx auth secret it should create a AUTH_SECRET="your-auth-secret" in your .env file.
now Initialize prisma npx prisma init it will create prisma folder inside it have prisma.schema file where you can write schema for user for this tutorial i will go with prisma documentation schema you can go through with this Prisma Docs.
Update your prisma.schema like this.
generator client {
provider = "prisma-client-js"
output = "../../node_modules/.prisma/client"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Account {
id String @id @default(cuid())
userId String @map("user_id")
type String
provider String
providerAccountId String @map("provider_account_id")
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@map("accounts")
}
model Session {
id String @id @default(cuid())
sessionToken String @unique @map("session_token")
userId String @map("user_id")
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("sessions")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
password String?
emailVerified DateTime? @map("email_verified")
image String?
accounts Account[]
sessions Session[]
@@map("users")
}
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
@@map("verification_tokens")
}
Now create a prisma.ts file in ./lib folder in your root of the project. and initialize prisma client like this.
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prismaSetup auth js. create a auth.ts file in root folder of your app. And add this content
import NextAuth from "next-auth"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [],
})
Now make another file inside app folder app/api/auth/[...nextauth]/route.ts and add this content.
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlersNow let's add Google OAuth first. Go to Google console and get Google client id and Google client secret and add this in .env file like this
AUTH_GOOGLE_ID="your-google-client-id"
AUTH_GOOGLE_SECRET="your-google-secret"
AUTH_SECRET="your-auth-secret"Now Import GoogleProvider from next-auth like this in auth.ts file.
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [Google],
})if you only want Google OAuth in your project the given instruction is enough to get started. You should skip to the Docker setup section.
Now let's add Credentials setup with auth js.
Import CredentialsProvider or Bcrypt or prisma client form lib/prisma.ts file and PrismaAdapter in auth.ts file.
import Credentials from "next-auth/providers/credentials"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "./utils/prisma"
import bcrypt from "bcryptjs"Add PrismaAdapter in NextAuth config and pass prisma (imported from ./lib/prisma.ts) in it.
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [Google]
})Now Setup credentials in Provider.
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [Google, Credentials({
credentials: {
email: {label: "email", type: "email", required: true, placeholder: "example@email.com"},
password: {label: "password", type: "password", required: true, placeholder: "********"},
name: {label: "name", type: "text", required: true, placeholder: "John Doe"},
},
authorize: async (credentials: any) => {
let user = null
const pwHash = await bcrypt.hash(credentials.password, 10)
user = await prisma.user.findUnique({
where: {
email: credentials.email
}
})
if(user) {
await prisma.user.update({
where: {
email: credentials.email
},
data: {
password: pwHash
}
})
}
if (!user) {
user = await prisma.user.create({
data: {
email: credentials.email,
password: pwHash,
name: credentials.name
}
})
}
return user
},
})],
})First create credentials for auth with email & password then add a callback named authorize and find the user and hash the password then return the user as it is.
First download docker from Download Docker and start the docker engine.
Now setup a postgressql with docker compose. create a docker-compost.yml file inside root of your project and update the file like this.
services:
postgres:
image: postgres
ports:
- 5432:5432
environment:
- POSTGRES_DB=postres // update db name accordingly
- POSTGRES_PASSWORD=admin // update db password accordingly
- POSTGRES_USER=admin // update db user accordingly
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:This code will pull the postgres database from hub.docker.com and start the container.
now run the container with this command docker compose up -d use the -d flag for detach mode recommended use -d flag. now it should start the docker container with postgressql. Now add the DATABSE_URL in the .env file like this.
DATABASE_URL="postgresql://admin:admin@localhost:5432/postres"Now docker is setup. let's migrate the sync the schema with database. Run the command npx prisma migrate dev --name init it should sync the schema with databse.
Now Generate PrismaClient npx prisma generate it should generate the prisma client.
Now use the authentication as you like. I would also give a sample code for it.
Make a folder named action in root of the file and inside the file create a auth.ts file and update like this.
"use server"
import { signIn, signOut } from "@/auth"
export const credentialsHandler = async (formData: FormData) => {
await signIn("credentials", formData, { callbackUrl: "/dashboard" })
}
export const googleHandler = async () => {
await signIn("google", { callbackUrl: "/dashboard" })
}
export const logoutHandler = async () => {
await signOut({ redirectTo: "/auth/signup" })
}I have a project setup with Shadcn so i'm using Input and Button component from it. Make a file inside app folder app/sign/page.tsx
and use the credentials and google provider in it. like this.
"use client"
import { credentialsHandler } from '@/action/auth'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Eye, EyeClosed } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import { useState } from 'react'
export default function Page() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [showPassword, setShowPassword] = useState(false)
return (
<section className='min-h-screen flex justify-center items-center px-4'>
<div className='max-w-[400px] min-w-[350px] p-4 rounded-md border border-gray-700'>
<h1 className='font-bold text-2xl text-center'>Welcome back</h1>
<div className='mt-4 w-full'>
<form action={
async (formData) => {
await credentialsHandler(formData)
}
} className='flex flex-col gap-4'>
<div>
<label htmlFor="email">Email</label>
<Input
onChange={(e) => setEmail(e.target.value)}
required
type="email"
id="email"
name="email"
placeholder='example@gmail.com'
value={email}
/>
</div>
<div className='relative'>
<label htmlFor="password">Password</label>
<Input
onChange={(e) => setPassword(e.target.value)}
required
type={showPassword ? "text" : "password"}
id="password"
name="password"
placeholder='*******'
value={password}
/>
<div className='absolute right-0 top-6 cursor-pointer'>
<Button onClick={() => setShowPassword(!showPassword)}>{showPassword ? <Eye /> : <EyeClosed />}</Button>
</div>
</div>
<div>
<Button className='w-full' type='submit'>Sign In</Button>
</div>
</form>
<div className='text-center text-gray-700 text-sm mt-4'>
Or
</div>
<div className='mt-4'>
<Button className='w-full py-6'>
<Image
src={`/google-logo.svg`}
className='mr-2 stroke-white'
height={30}
width={30}
alt="google"
/>
Sign in with Google
</Button>
</div>
<div className='mt-4'>
<p className='text-center text-sm'>Don't have an account? <Link href="/auth/signup" className='text-blue-500'>Sign Up</Link></p>
</div>
</div>
</div>
</section>
)
}
That's it Thank you for reading share it with you friends and collegues. Signing offsignOverview.