Provides guidance for integrating authentication into full-stack applications.
/plugin marketplace add gaurangrshah/gsc-plugins/plugin install appgen@gsc-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Provides guidance for integrating authentication into full-stack applications.
This skill helps the appgen agent integrate authentication providers (Auth.js, Clerk, Lucia) into applications.
Best for: Next.js applications with email/password or OAuth
Features:
Installation:
npm install next-auth @auth/prisma-adapter
Best for: Quick setup, modern UI, managed auth
Features:
Installation:
npm install @clerk/nextjs
Best for: API-only apps, full control, minimal dependencies
Features:
Installation:
npm install lucia @lucia-auth/adapter-prisma
npm install next-auth @auth/prisma-adapter
npm install bcryptjs
npm install -D @types/bcryptjs
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
passwordHash String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accounts Account[]
sessions Session[]
@@index([email])
}
model Account {
id String @id @default(cuid())
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)
@@unique([provider, providerAccountId])
@@index([userId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
Create lib/auth.ts:
import { NextAuthOptions } from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import CredentialsProvider from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';
import { prisma } from '@/lib/db';
import bcrypt from 'bcryptjs';
import { z } from 'zod';
const credentialsSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
CredentialsProvider({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
throw new Error('Invalid credentials');
}
const { email, password } = credentialsSchema.parse(credentials);
const user = await prisma.user.findUnique({
where: { email },
});
if (!user || !user.passwordHash) {
throw new Error('Invalid credentials');
}
const isValidPassword = await bcrypt.compare(
password,
user.passwordHash
);
if (!isValidPassword) {
throw new Error('Invalid credentials');
}
return {
id: user.id,
email: user.email,
name: user.name,
};
},
}),
],
session: {
strategy: 'jwt',
},
pages: {
signIn: '/login',
signOut: '/logout',
error: '/error',
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
}
return session;
},
},
};
Create app/api/auth/[...nextauth]/route.ts:
import NextAuth from 'next-auth';
import { authOptions } from '@/lib/auth';
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Create components/providers/auth-provider.tsx:
'use client';
import { SessionProvider } from 'next-auth/react';
export function AuthProvider({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
Wrap app in app/layout.tsx:
import { AuthProvider } from '@/components/providers/auth-provider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<AuthProvider>{children}</AuthProvider>
</body>
</html>
);
}
Create middleware (middleware.ts):
import { withAuth } from 'next-auth/middleware';
export default withAuth({
pages: {
signIn: '/login',
},
});
export const config = {
matcher: ['/dashboard/:path*', '/api/users/:path*'],
};
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
export function UserButton() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <div>Loading...</div>;
}
if (!session) {
return <button onClick={() => signIn()}>Sign In</button>;
}
return (
<div>
<p>Signed in as {session.user.email}</p>
<button onClick={() => signOut()}>Sign Out</button>
</div>
);
}
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
export async function GET() {
const session = await getServerSession(authOptions);
if (!session) {
return new Response('Unauthorized', { status: 401 });
}
// Use session.user.id...
}
npm install lucia @lucia-auth/adapter-prisma
npm install bcryptjs
npm install -D @types/bcryptjs
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions Session[]
@@index([email])
}
model Session {
id String @id @default(cuid())
userId String
expiresAt DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
Create lib/auth.ts:
import { Lucia } from 'lucia';
import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
import { prisma } from './db';
const adapter = new PrismaAdapter(prisma.session, prisma.user);
export const lucia = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: process.env.NODE_ENV === 'production',
},
},
getUserAttributes: (attributes) => {
return {
email: attributes.email,
};
},
});
declare module 'lucia' {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: {
email: string;
};
}
}
import { lucia } from '@/lib/auth';
import { prisma } from '@/lib/db';
import bcrypt from 'bcryptjs';
import { z } from 'zod';
const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
app.post('/api/auth/register', async (c) => {
try {
const body = await c.req.json();
const { email, password } = registerSchema.parse(body);
const passwordHash = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: { email, passwordHash },
});
const session = await lucia.createSession(user.id, {});
const sessionCookie = lucia.createSessionCookie(session.id);
c.header('Set-Cookie', sessionCookie.serialize());
return c.json({ data: { userId: user.id } }, 201);
} catch (error) {
// Handle errors...
}
});
app.post('/api/auth/login', async (c) => {
try {
const body = await c.req.json();
const { email, password } = registerSchema.parse(body);
const user = await prisma.user.findUnique({ where: { email } });
if (!user) {
return c.json({
error: { code: 'INVALID_CREDENTIALS', message: 'Invalid email or password' },
}, 401);
}
const validPassword = await bcrypt.compare(password, user.passwordHash);
if (!validPassword) {
return c.json({
error: { code: 'INVALID_CREDENTIALS', message: 'Invalid email or password' },
}, 401);
}
const session = await lucia.createSession(user.id, {});
const sessionCookie = lucia.createSessionCookie(session.id);
c.header('Set-Cookie', sessionCookie.serialize());
return c.json({ data: { userId: user.id } });
} catch (error) {
// Handle errors...
}
});
import { lucia } from '@/lib/auth';
export async function authMiddleware(c: Context, next: Next) {
const sessionId = getCookie(c, lucia.sessionCookieName);
if (!sessionId) {
return c.json({
error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
}, 401);
}
const { session, user } = await lucia.validateSession(sessionId);
if (!session) {
return c.json({
error: { code: 'INVALID_SESSION', message: 'Invalid or expired session' },
}, 401);
}
c.set('user', user);
c.set('session', session);
await next();
}
Add to .env.example:
Auth.js:
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="your-secret-here" # Generate with: openssl rand -base64 32
# OAuth (if using)
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
Lucia:
# Session secret
SESSION_SECRET="your-secret-here"
v1.0 (2024-12-13)
Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.