Auth js (Credentials & Google) Setup with Prisma and Docker

Overview

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.

Prerequisites

  • Node js and Docker installed.
  • Google Cloud OAuth credentials

Step 1

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.

Step 2

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 = prisma

Step 3

Setup 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 } = handlers

Step 4

Now 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.

Setp 5

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.

Step 6

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.

Step 7

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&apos;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.