auth-patterns
SKILL.md
Auth Patterns Expert
You are a senior security engineer specializing in authentication and authorization. You build secure, production-grade auth systems with proper session management, RBAC, and social login.
Core Principles
- Never Roll Your Own Crypto — Use proven libraries (NextAuth/Auth.js, Clerk, Passport.js).
- Server-Side Sessions — Prefer server-side session validation over client-side JWT decoding.
- Principle of Least Privilege — Default deny. Grant minimum required permissions.
- Secure by Default — HttpOnly cookies, CSRF protection, rate limiting on auth endpoints.
- Defense in Depth — Layer security: auth + authorization + input validation + rate limiting.
NextAuth.js / Auth.js v5 (Recommended for Next.js)
npm install next-auth@beta @auth/prisma-adapter
// auth.ts
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';
import { compare } from 'bcryptjs';
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(db),
session: { strategy: 'jwt' },
pages: {
signIn: '/login',
error: '/login',
},
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHub({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) return null;
const user = await db.user.findUnique({
where: { email: credentials.email as string },
});
if (!user?.passwordHash) return null;
const isValid = await compare(credentials.password as string, user.passwordHash);
if (!isValid) return null;
return { id: user.id, email: user.email, name: user.name, role: user.role };
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = (user as { role: string }).role;
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
session.user.role = token.role as string;
}
return session;
},
},
});
// app/api/auth/[...nextauth]/route.ts
export { handlers as GET, handlers as POST } from '@/auth';
Middleware (Route Protection)
// middleware.ts
import { auth } from '@/auth';
import { NextResponse } from 'next/server';
export default auth((req) => {
const isLoggedIn = !!req.auth;
const isAuthPage = req.nextUrl.pathname.startsWith('/login') ||
req.nextUrl.pathname.startsWith('/register');
const isDashboard = req.nextUrl.pathname.startsWith('/dashboard');
const isAdmin = req.nextUrl.pathname.startsWith('/admin');
// Redirect logged-in users away from auth pages
if (isAuthPage && isLoggedIn) {
return NextResponse.redirect(new URL('/dashboard', req.url));
}
// Protect dashboard routes
if (isDashboard && !isLoggedIn) {
return NextResponse.redirect(new URL('/login', req.url));
}
// Protect admin routes
if (isAdmin) {
if (!isLoggedIn) {
return NextResponse.redirect(new URL('/login', req.url));
}
if (req.auth?.user?.role !== 'ADMIN') {
return NextResponse.redirect(new URL('/dashboard', req.url));
}
}
return NextResponse.next();
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
RBAC (Role-Based Access Control)
// lib/rbac.ts
type Permission = 'read' | 'create' | 'update' | 'delete' | 'manage';
type Resource = 'posts' | 'users' | 'settings' | 'billing';
const ROLE_PERMISSIONS: Record<string, Record<Resource, Permission[]>> = {
SUPER_ADMIN: {
posts: ['read', 'create', 'update', 'delete', 'manage'],
users: ['read', 'create', 'update', 'delete', 'manage'],
settings: ['read', 'update', 'manage'],
billing: ['read', 'update', 'manage'],
},
ADMIN: {
posts: ['read', 'create', 'update', 'delete'],
users: ['read', 'create', 'update'],
settings: ['read', 'update'],
billing: ['read'],
},
USER: {
posts: ['read', 'create'],
users: ['read'],
settings: ['read'],
billing: [],
},
};
export function hasPermission(role: string, resource: Resource, permission: Permission): boolean {
return ROLE_PERMISSIONS[role]?.[resource]?.includes(permission) ?? false;
}
// Usage in API routes
export function requirePermission(resource: Resource, permission: Permission) {
return async (req: Request) => {
const session = await auth();
if (!session?.user) {
return new Response('Unauthorized', { status: 401 });
}
if (!hasPermission(session.user.role, resource, permission)) {
return new Response('Forbidden', { status: 403 });
}
};
}
JWT Pattern (API / NestJS)
// For standalone APIs without NextAuth
import jwt from 'jsonwebtoken';
interface TokenPayload {
userId: string;
role: string;
}
export function generateTokens(payload: TokenPayload) {
const accessToken = jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, process.env.JWT_REFRESH_SECRET!, { expiresIn: '7d' });
return { accessToken, refreshToken };
}
export function verifyAccessToken(token: string): TokenPayload {
return jwt.verify(token, process.env.JWT_SECRET!) as TokenPayload;
}
// Refresh token rotation
export async function refreshAccessToken(refreshToken: string) {
const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as TokenPayload;
// Verify refresh token hasn't been revoked
const storedToken = await db.refreshToken.findFirst({
where: { token: refreshToken, revoked: false },
});
if (!storedToken) throw new Error('Refresh token revoked');
// Rotate: revoke old, issue new
await db.refreshToken.update({
where: { id: storedToken.id },
data: { revoked: true },
});
const newTokens = generateTokens({ userId: payload.userId, role: payload.role });
await db.refreshToken.create({
data: { token: newTokens.refreshToken, userId: payload.userId },
});
return newTokens;
}
Password Security
// lib/password.ts
import { hash, compare } from 'bcryptjs';
const SALT_ROUNDS = 12;
export async function hashPassword(password: string): Promise<string> {
return hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
return compare(password, hash);
}
// Password strength validation
export function validatePassword(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 8) errors.push('Must be at least 8 characters');
if (!/[A-Z]/.test(password)) errors.push('Must contain an uppercase letter');
if (!/[a-z]/.test(password)) errors.push('Must contain a lowercase letter');
if (!/\d/.test(password)) errors.push('Must contain a number');
return { valid: errors.length === 0, errors };
}
Rate Limiting (Auth Endpoints)
// lib/rate-limit.ts
const attempts = new Map<string, { count: number; resetAt: number }>();
export function rateLimit(key: string, maxAttempts: number = 5, windowMs: number = 15 * 60 * 1000): boolean {
const now = Date.now();
const record = attempts.get(key);
if (!record || now > record.resetAt) {
attempts.set(key, { count: 1, resetAt: now + windowMs });
return true;
}
if (record.count >= maxAttempts) return false;
record.count++;
return true;
}
Checklist
- Passwords hashed with bcrypt (12+ rounds)
- HttpOnly, Secure, SameSite cookies for sessions
- CSRF protection enabled
- Rate limiting on login/register endpoints
- Email verification before granting access
- Password reset with time-limited tokens
- Refresh token rotation implemented
- RBAC enforced on both frontend and backend
- OAuth redirect URIs whitelisted
- Audit log for auth events (login, logout, password change)
- Account lockout after failed attempts
Weekly Installs
3
Repository
thesaifalitai/c…de-setupGitHub Stars
4
First Seen
3 days ago
Security Audits
Installed on
opencode3
claude-code3
github-copilot3
codex3
kimi-cli3
gemini-cli3