skills/auth-patterns/SKILL.md
ALWAYS trigger for ANY task involving authentication, authorization, login, signup, registration, NextAuth.js, Auth.js, Clerk, Firebase Auth, Supabase Auth, JWT tokens, refresh tokens, OAuth, Google login, GitHub login, social auth, session management, RBAC, role-based access control, MFA, two-factor authentication, password reset, email verification, or any auth-related development task.
npx skillsauth add thesaifalitai/claude-setup auth-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
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.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).*)'],
};
// 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 });
}
};
}
// 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;
}
// 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 };
}
// 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;
}
development
Use when building Vue 3 applications with Composition API, Nuxt 3, or Quasar. Invoke for Pinia, TypeScript, PWA, Capacitor mobile apps, Vite configuration.
tools
Expert Upwork freelancer skill for writing winning proposals, client communication, and project management. ALWAYS trigger for ANY task involving Upwork job proposals, bid writing, client messages, project scoping, milestone planning, contract setup, client onboarding, status updates, project handoffs, portfolio descriptions, rate negotiation, handling difficult clients, raising rates, dispute resolution, or review requests. Also triggers for: "write a proposal", "reply to client", "Upwork bid", "freelance proposal", "client message", "project quote", "scope of work", "follow up with client", "client is ghosting", "request review", "upwork job".
development
UI component code implementation specialist. Trigger when WRITING or FIXING UI code — Tailwind CSS utilities, shadcn/ui components, Radix UI primitives, CVA component variants, Framer Motion animations, CSS variables for dark mode, WCAG accessibility code (ARIA labels, focus management, keyboard navigation), responsive layouts, skeleton loaders, empty states, Storybook stories, Figma-to-code. Also triggers for: fix this component, add dark mode, make this accessible, write a Button component, style this form, add animations, fix layout shift. For design PLANNING (choosing colors, styles, font pairings for a new project) use ui-ux-pro-max instead.
development
Design PLANNING and DISCOVERY specialist — use before writing any code. Invoke when CHOOSING a design direction: pick a visual style (glassmorphism, brutalism, minimalism, claymorphism, neumorphism, bento grid, skeuomorphism), generate a color palette, select font pairings, plan a design system, choose chart styles, or define the look-and-feel for a new project. Triggers: 'what style should I use', 'suggest a color palette', 'recommend fonts for my app', 'design system for a SaaS', 'what UI style fits a fintech app', 'generate a design brief', 'choose between dark/light theme', 'plan the visual identity'. Covers 50 styles, 97 palettes, 57 font pairings across React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui stacks. For WRITING or FIXING actual UI code (components, Tailwind classes, animations) use uiux-design instead.