I built auth from scratch five times. Each time: 3-5 days. Each time I swore never again.
The 60-line solution with Auth.js v5:
import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import Google from "next-auth/providers/google";
import GitHub from "next-auth/providers/github";
import Credentials from "next-auth/providers/credentials";
import { db } from "@/lib/db";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(db),
session: { strategy: "jwt" },
pages: { signIn: "/login" },
providers: [
Google({ clientId: process.env.AUTH_GOOGLE_ID, clientSecret: process.env.AUTH_GOOGLE_SECRET }),
GitHub({ clientId: process.env.AUTH_GITHUB_ID, clientSecret: process.env.AUTH_GITHUB_SECRET }),
Credentials({
credentials: { email: { type: "email" }, password: { type: "password" } },
async authorize(creds) {
if (!creds?.email || !creds?.password) return null;
const user = await db.user.findUnique({ where: { email: creds.email as string } });
if (!user?.password) return null;
if (creds.password !== user.password) return null;
return { id: user.id, email: user.email, name: user.name };
},
}),
],
callbacks: {
async jwt({ token }) {
if (!token.sub) return token;
const user = await db.user.findUnique({ where: { id: token.sub }, select: { role: true } });
if (user) token.role = user.role;
return token;
},
async session({ token, session }) {
if (token.sub) { session.user.id = token.sub; session.user.role = token.role as string; }
return session;
},
},
});
Route handler (2 lines):
import { handlers } from "@/lib/auth";
export const { GET, POST } = handlers;
Protect any page (2 lines):
const session = await auth();
if (!session?.user) redirect("/login");
Google + GitHub + email/password. JWT sessions. Role-based access. Done.
I packaged this into LaunchKit with Stripe billing, AI chat, email, and a full dashboard. Clone and start building your product on day one.
Top comments (0)