Implements authentication with Auth.js/NextAuth.js v5 including OAuth providers, credentials, sessions, and route protection. Use when adding authentication to Next.js, configuring OAuth providers, or protecting routes.
Adds authentication to Next.js apps using Auth.js v5 with OAuth providers, credentials, and session management. Use when building login systems, configuring OAuth (GitHub/Google), or protecting routes with middleware.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Flexible authentication library for Next.js with 80+ OAuth providers and database adapters.
Install:
npm install next-auth@beta
Generate secret:
npx auth secret
This adds AUTH_SECRET to .env.local.
// auth.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
GitHub,
Google,
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
// Validate credentials against database
const user = await getUserFromDb(
credentials.email as string,
credentials.password as string
);
if (!user) return null;
return {
id: user.id,
email: user.email,
name: user.name,
};
},
}),
],
});
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
# .env.local
AUTH_SECRET=your-generated-secret
# OAuth Providers
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
import GitHub from 'next-auth/providers/github';
export const { handlers, auth } = NextAuth({
providers: [
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
}),
],
});
import Google from 'next-auth/providers/google';
export const { handlers, auth } = NextAuth({
providers: [
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
authorization: {
params: {
prompt: 'consent',
access_type: 'offline',
response_type: 'code',
},
},
}),
],
});
import Discord from 'next-auth/providers/discord';
export const { handlers, auth } = NextAuth({
providers: [Discord],
});
import Credentials from 'next-auth/providers/credentials';
import { compare } from 'bcryptjs';
export const { handlers, auth } = NextAuth({
providers: [
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await prisma.user.findUnique({
where: { email: credentials.email as string },
});
if (!user || !user.password) {
return null;
}
const isValid = await compare(
credentials.password as string,
user.password
);
if (!isValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
image: user.image,
};
},
}),
],
pages: {
signIn: '/login',
},
});
// In Server Components
import { auth } from '@/auth';
export default async function Page() {
const session = await auth();
if (!session) {
return <div>Please sign in</div>;
}
return (
<div>
<p>Welcome, {session.user?.name}</p>
<p>Email: {session.user?.email}</p>
</div>
);
}
'use client';
import { useSession } from 'next-auth/react';
export function UserInfo() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <div>Loading...</div>;
}
if (status === 'unauthenticated') {
return <div>Not signed in</div>;
}
return (
<div>
<p>Signed in as {session?.user?.name}</p>
</div>
);
}
// app/layout.tsx
import { SessionProvider } from 'next-auth/react';
import { auth } from '@/auth';
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth();
return (
<html lang="en">
<body>
<SessionProvider session={session}>
{children}
</SessionProvider>
</body>
</html>
);
}
// app/actions.ts
'use server';
import { signIn, signOut } from '@/auth';
export async function handleSignIn(provider: string) {
await signIn(provider, { redirectTo: '/dashboard' });
}
export async function handleSignOut() {
await signOut({ redirectTo: '/' });
}
'use client';
import { signIn, signOut } from 'next-auth/react';
export function SignInButton() {
return (
<button onClick={() => signIn('github')}>
Sign in with GitHub
</button>
);
}
export function SignOutButton() {
return (
<button onClick={() => signOut()}>
Sign out
</button>
);
}
// Or using server actions
import { handleSignIn, handleSignOut } from './actions';
export function AuthButtons() {
return (
<div>
<form action={() => handleSignIn('github')}>
<button type="submit">Sign in with GitHub</button>
</form>
<form action={handleSignOut}>
<button type="submit">Sign out</button>
</form>
</div>
);
}
// middleware.ts
export { auth as middleware } from '@/auth';
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
// middleware.ts
import { auth } from '@/auth';
import { NextResponse } from 'next/server';
export default auth((req) => {
const isLoggedIn = !!req.auth;
const isOnDashboard = req.nextUrl.pathname.startsWith('/dashboard');
const isOnAuth = req.nextUrl.pathname.startsWith('/login');
if (isOnDashboard && !isLoggedIn) {
return NextResponse.redirect(new URL('/login', req.url));
}
if (isOnAuth && isLoggedIn) {
return NextResponse.redirect(new URL('/dashboard', req.url));
}
return NextResponse.next();
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
// auth.ts
export const { handlers, auth } = NextAuth({
providers: [...],
callbacks: {
authorized: async ({ auth, request }) => {
const isLoggedIn = !!auth?.user;
const isProtected = request.nextUrl.pathname.startsWith('/dashboard');
if (isProtected && !isLoggedIn) {
return false; // Redirects to signIn page
}
return true;
},
},
});
// app/api/protected/route.ts
import { auth } from '@/auth';
import { NextResponse } from 'next/server';
export const GET = auth(function GET(req) {
if (!req.auth) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
return NextResponse.json({
user: req.auth.user,
message: 'Protected data',
});
});
// auth.ts
export const { handlers, auth } = NextAuth({
providers: [...],
callbacks: {
jwt: async ({ token, user, account }) => {
// Add user data to token on sign in
if (user) {
token.id = user.id;
token.role = user.role;
}
// Add access token from OAuth
if (account) {
token.accessToken = account.access_token;
}
return token;
},
session: async ({ session, token }) => {
// Add token data to session
if (token) {
session.user.id = token.id as string;
session.user.role = token.role as string;
session.accessToken = token.accessToken as string;
}
return session;
},
},
});
callbacks: {
signIn: async ({ user, account, profile }) => {
// Allow OAuth sign in
if (account?.provider !== 'credentials') {
return true;
}
// Check if user is verified
const existingUser = await getUserById(user.id);
if (!existingUser?.emailVerified) {
return false;
}
return true;
},
}
npm install @auth/prisma-adapter
// auth.ts
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/lib/prisma';
export const { handlers, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [...],
session: {
strategy: 'database', // or 'jwt'
},
});
npm install @auth/drizzle-adapter
import { DrizzleAdapter } from '@auth/drizzle-adapter';
import { db } from '@/db';
export const { handlers, auth } = NextAuth({
adapter: DrizzleAdapter(db),
providers: [...],
});
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: string;
} & DefaultSession['user'];
accessToken?: string;
}
interface User {
role: string;
}
}
declare module 'next-auth/jwt' {
interface JWT {
id: string;
role: string;
accessToken?: string;
}
}
// auth.ts
export const { handlers, auth } = NextAuth({
providers: [...],
pages: {
signIn: '/login',
signOut: '/logout',
error: '/auth/error',
verifyRequest: '/auth/verify',
newUser: '/onboarding',
},
});
// app/login/page.tsx
import { signIn } from '@/auth';
export default function LoginPage() {
return (
<div className="flex flex-col gap-4 max-w-md mx-auto mt-20">
<h1 className="text-2xl font-bold">Sign In</h1>
<form
action={async () => {
'use server';
await signIn('github', { redirectTo: '/dashboard' });
}}
>
<button className="w-full p-2 bg-gray-900 text-white rounded">
Sign in with GitHub
</button>
</form>
<form
action={async () => {
'use server';
await signIn('google', { redirectTo: '/dashboard' });
}}
>
<button className="w-full p-2 bg-blue-600 text-white rounded">
Sign in with Google
</button>
</form>
</div>
);
}
| Mistake | Fix |
|---|---|
| Missing AUTH_SECRET | Run npx auth secret |
| Exposed credentials | Use environment variables |
| JWT-only with credentials | Consider database sessions |
| Missing session provider | Wrap app with SessionProvider |
| No redirect after auth | Set redirectTo option |
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.