OAuth Setup
intermediate*Production-ready OAuth authentication with Auth.js (NextAuth v5). Includes GitHub and Google providers with database session storage.
authoauthnextauthsession
Tested on⬢20▲16⚛19TS5.9
$ bunx sinew add auth/oauth-setupInteractive demo coming soon
Prerequisites
- Database
PostgreSQL, MySQL, or any Prisma-supported database
- OAuth App
GitHub and/or Google OAuth credentials
Tested With
2/2 passingNext.js15.0
Auth.js5.0-beta
1The Problem
Implementing OAuth from scratch is complex and error-prone:
- Token management and refresh
- CSRF protection
- Session handling
- Database schema for users and accounts
- Proper security headers
2The Solution
Auth.js handles all the complexity while giving you full control over the authentication flow.
3Files
lib/auth.ts
lib/auth.tsTypeScript
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "./db";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
callbacks: {
session({ session, user }) {
session.user.id = user.id;
return session;
},
},
pages: {
signIn: "/login",
error: "/login",
},
});app/api/auth/[...nextauth]/route.ts
app/api/auth/[...nextauth]/route.tsTypeScript
import { handlers } from "@/lib/auth";
export const { GET, POST } = handlers;middleware.ts
middleware.tsTypeScript
import { auth } from "@/lib/auth";
export default auth((req) => {
const isLoggedIn = !!req.auth;
const isOnDashboard = req.nextUrl.pathname.startsWith("/dashboard");
if (isOnDashboard && !isLoggedIn) {
return Response.redirect(new URL("/login", req.url));
}
});
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};components/auth-button.tsx
components/auth-button.tsxTypeScript
import { signIn, signOut, auth } from "@/lib/auth";
export async function SignInButton() {
const session = await auth();
if (session) {
return (
<form
action={async () => {
"use server";
await signOut();
}}
>
<button type="submit">Sign out</button>
</form>
);
}
return (
<form
action={async () => {
"use server";
await signIn("github");
}}
>
<button type="submit">Sign in with GitHub</button>
</form>
);
}prisma/schema.prisma
prisma/schema.prismaprisma
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
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?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}.env.example
.env.exampleBash
AUTH_SECRET="your-secret-here" # Generate with: openssl rand -base64 32
# GitHub OAuth
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"
# Google OAuth
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"4Dependencies
$ bun add next-auth @auth/prisma-adapter5Configuration
GitHub OAuth App
- Go to [GitHub Developer Settings](https://github.com/settings/developers)
- Create a new OAuth App
- Set callback URL to
http://localhost:3000/api/auth/callback/github(dev) or your production URL
Google OAuth
- Go to [Google Cloud Console](https://console.cloud.google.com)
- Create OAuth 2.0 credentials
- Add authorized redirect URI:
http://localhost:3000/api/auth/callback/google
6Usage
Get Session in Server Components
import { auth } from "@/lib/auth";
export default async function Dashboard() {
const session = await auth();
if (!session) {
redirect("/login");
}
return <div>Welcome, {session.user.name}!</div>;
}TypeScript
Get Session in Client Components
"use client";
import { useSession } from "next-auth/react";
export function UserGreeting() {
const { data: session, status } = useSession();
if (status === "loading") return <div>Loading...</div>;
if (!session) return <div>Not signed in</div>;
return <div>Hello, {session.user.name}!</div>;
}TypeScript
7Troubleshooting
"CSRF token mismatch" error
Make sure your AUTH_SECRET is set and consistent across all environments.
Session not persisting
Check that your database is correctly configured and the Prisma adapter can write to it.