Next.js 14 Auth with Auth.js and Drizzle ORM

Next Auth is a widely-used library within the Next.js ecosystem, designed for managing user authentication and authorization in your application.

In this tutorial, I guide you through the process of integrating Next Auth into your Next.js 14 app, utilizing the Drizzle ORM adapter. Everything will be using Server Actions and Server Components.

This setup will enable users to log in to your app using social sign-in providers such as GitHub, with their user data securely stored in your database via Drizzle ORM.

The instructions are a modified version of what's found on the authjs website: https://authjs.dev/reference/nextjs

warning

The middleware features seem a bit buggy right now, so avoid using middleware until it's more stable: https://authjs.dev/reference/nextjs#in-middleware

Setup

step 1:

Install Next Auth 5

npm install next-auth@beta @auth/core
npm install next-auth@beta @auth/core
npm install next-auth@beta @auth/core
npm install next-auth@beta @auth/core
step 2:

Create a new file inside of src called auth.ts.

  • src
    • app
    • auth.ts
    • ...
step 3:

Add the following code to auth.ts

auth.ts
import type { NextAuthConfig } from "next-auth"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/GitHub"

export const authConfig = {
providers: [GitHub],
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import type { NextAuthConfig } from "next-auth"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/GitHub"

export const authConfig = {
providers: [GitHub],
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import type { NextAuthConfig } from "next-auth"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/GitHub"

export const authConfig = {
providers: [GitHub],
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import type { NextAuthConfig } from "next-auth"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/GitHub"

export const authConfig = {
providers: [GitHub],
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
step 4:

Create a new file for the auth HTTP routes.

  • src
    • app
      • api
        • auth
          • [...nextauth]
            • route.ts
    • ...
step 5:

Add the following code to api/auth/[...nextauth]/route.ts

route.ts
import { handlers } from "@/auth";

export const { GET, POST } = handlers;
route.ts
import { handlers } from "@/auth";

export const { GET, POST } = handlers;
route.ts
import { handlers } from "@/auth";

export const { GET, POST } = handlers;
route.ts
import { handlers } from "@/auth";

export const { GET, POST } = handlers;
step 6:

Add the following environment variables to your .env.local file.

AUTH_GITHUB_ID=...
AUTH_GITHUB_SECRET=...
AUTH_SECRET=...
AUTH_GITHUB_ID=...
AUTH_GITHUB_SECRET=...
AUTH_SECRET=...
AUTH_GITHUB_ID=...
AUTH_GITHUB_SECRET=...
AUTH_SECRET=...
AUTH_GITHUB_ID=...
AUTH_GITHUB_SECRET=...
AUTH_SECRET=...

You can generate an AUTH_SECRET using openssl rand -hex 32, or just click this button:

Github OAuth

step 7:

Follow the instruction in the video (timestamp 4:16) to create a new GitHub OAuth App and add the env vars to your .env.local file.

step 8:

Test login is working by navigating to localhost:3000/api/auth/signin and clicking the GitHub button.

Drizzle

step 9:

Follow the instruction in the drizzle adapter docs to install the drizzle adapter, and add the correct schema code for your database.

step 10:

Push the updated database schema to the database.

npx drizzle-kit push
npx drizzle-kit push
npx drizzle-kit push
npx drizzle-kit push
step 11:

Update the auth.ts file to use the drizzle adapter.

auth.ts
import type { NextAuthConfig } from "next-auth"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/GitHub"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "@/db"

export const authConfig = {
adapter: DrizzleAdapter(db),
providers: [GitHub],
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import type { NextAuthConfig } from "next-auth"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/GitHub"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "@/db"

export const authConfig = {
adapter: DrizzleAdapter(db),
providers: [GitHub],
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import type { NextAuthConfig } from "next-auth"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/GitHub"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "@/db"

export const authConfig = {
adapter: DrizzleAdapter(db),
providers: [GitHub],
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import type { NextAuthConfig } from "next-auth"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/GitHub"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "@/db"

export const authConfig = {
adapter: DrizzleAdapter(db),
providers: [GitHub],
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
step 12:

Create a new folder called types/next-auth.d.ts in the root of the project and add the following code:

types/next-auth.d.ts
import NextAuth, { DefaultSession } from "next-auth"

declare module "next-auth" {
interface Session {
user: {
id: string
} & DefaultSession["user"]
}
}
types/next-auth.d.ts
import NextAuth, { DefaultSession } from "next-auth"

declare module "next-auth" {
interface Session {
user: {
id: string
} & DefaultSession["user"]
}
}
types/next-auth.d.ts
import NextAuth, { DefaultSession } from "next-auth"

declare module "next-auth" {
interface Session {
user: {
id: string
} & DefaultSession["user"]
}
}
types/next-auth.d.ts
import NextAuth, { DefaultSession } from "next-auth"

declare module "next-auth" {
interface Session {
user: {
id: string
} & DefaultSession["user"]
}
}
step 13:

Keep track of the user's id in the session.

auth.ts
import NextAuth, { NextAuthConfig } from "next-auth"
import GitHub from "next-auth/providers/GitHub"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "@/db"

export const authConfig = {
providers: [GitHub],
adapter: DrizzleAdapter(db),
callbacks: {
async session({session, user}) {
session.user.id = user.id
return session
},
}
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import NextAuth, { NextAuthConfig } from "next-auth"
import GitHub from "next-auth/providers/GitHub"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "@/db"

export const authConfig = {
providers: [GitHub],
adapter: DrizzleAdapter(db),
callbacks: {
async session({session, user}) {
session.user.id = user.id
return session
},
}
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import NextAuth, { NextAuthConfig } from "next-auth"
import GitHub from "next-auth/providers/GitHub"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "@/db"

export const authConfig = {
providers: [GitHub],
adapter: DrizzleAdapter(db),
callbacks: {
async session({session, user}) {
session.user.id = user.id
return session
},
}
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)
auth.ts
import NextAuth, { NextAuthConfig } from "next-auth"
import GitHub from "next-auth/providers/GitHub"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "@/db"

export const authConfig = {
providers: [GitHub],
adapter: DrizzleAdapter(db),
callbacks: {
async session({session, user}) {
session.user.id = user.id
return session
},
}
} satisfies NextAuthConfig

export const {
handlers,
auth,
signOut
} = NextAuth(authConfig)

Usage

me/page.tsx
import { auth, signOut } from "@/auth"

import { redirect } from "next/navigation"

import SignoutButton from "./sign-out-button"

export default async function ProfilePage() {
const session = await auth()

if (!session?.user) {
redirect("/api/auth/signin?callbackUrl=/me")
}

return (
<>
{session.user.id}
{session.user.name}
{session.user.email}
<Image src={session.user.image} />

<SignoutButton
signOut={async () => {
"use server"
await signOut({redirectTo: "/"})
}}
/>
</>
)
}
me/page.tsx
import { auth, signOut } from "@/auth"

import { redirect } from "next/navigation"

import SignoutButton from "./sign-out-button"

export default async function ProfilePage() {
const session = await auth()

if (!session?.user) {
redirect("/api/auth/signin?callbackUrl=/me")
}

return (
<>
{session.user.id}
{session.user.name}
{session.user.email}
<Image src={session.user.image} />

<SignoutButton
signOut={async () => {
"use server"
await signOut({redirectTo: "/"})
}}
/>
</>
)
}
me/page.tsx
import { auth, signOut } from "@/auth"

import { redirect } from "next/navigation"

import SignoutButton from "./sign-out-button"

export default async function ProfilePage() {
const session = await auth()

if (!session?.user) {
redirect("/api/auth/signin?callbackUrl=/me")
}

return (
<>
{session.user.id}
{session.user.name}
{session.user.email}
<Image src={session.user.image} />

<SignoutButton
signOut={async () => {
"use server"
await signOut({redirectTo: "/"})
}}
/>
</>
)
}
me/page.tsx
import { auth, signOut } from "@/auth"

import { redirect } from "next/navigation"

import SignoutButton from "./sign-out-button"

export default async function ProfilePage() {
const session = await auth()

if (!session?.user) {
redirect("/api/auth/signin?callbackUrl=/me")
}

return (
<>
{session.user.id}
{session.user.name}
{session.user.email}
<Image src={session.user.image} />

<SignoutButton
signOut={async () => {
"use server"
await signOut({redirectTo: "/"})
}}
/>
</>
)
}

Middleware

middleware.ts
auth.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth';

export default NextAuth(authConfig).auth;

export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ['/((?!api|_next/static|_next/image|.png).*)'],
};
middleware.ts
auth.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth';

export default NextAuth(authConfig).auth;

export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ['/((?!api|_next/static|_next/image|.png).*)'],
};
middleware.ts
auth.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth';

export default NextAuth(authConfig).auth;

export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ['/((?!api|_next/static|_next/image|.png).*)'],
};
middleware.ts
auth.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth';

export default NextAuth(authConfig).auth;

export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ['/((?!api|_next/static|_next/image|.png).*)'],
};

Complete Code

https://github.com/meech-ward/next-auth-example


References