Auth security
Skill smicolon/ai-kit/packs/better-auth/skills/auth-security
Convention packs for any AI coding tool - agents, skills, commands, and rules for 15 tools including Claude Code, Cursor, Windsurf, and Copilot
npx -y skills add smicolon/ai-kit --skill auth-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Auto-enforce authentication security best practices. Activates when implementing password policies, rate limiting, session security, CSRF protection, or security headers in auth flows.
SKILL.md
9.7 KB, as published. Nobody here has run it
Auth Security Patterns
This skill enforces authentication security best practices for Better Auth implementations.
Password Security
Strong Password Policy
import { betterAuth } from 'better-auth'
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
password: {
minLength: 12,
maxLength: 128,
requireLowercase: true,
requireUppercase: true,
requireNumber: true,
requireSpecialChar: true,
// Custom validation
validate: (password) => {
// Check against common passwords
if (commonPasswords.includes(password.toLowerCase())) {
return 'Password is too common'
}
// Check for repeated characters
if (/(.)\1{2,}/.test(password)) {
return 'Password cannot have 3+ repeated characters'
}
return true
},
},
},
})
Password Hashing
// Better Auth uses bcrypt by default with cost factor 10
// For higher security requirements:
export const auth = betterAuth({
advanced: {
password: {
hash: async (password) => {
const salt = await bcrypt.genSalt(12) // Higher cost
return bcrypt.hash(password, salt)
},
verify: async (password, hash) => {
return bcrypt.compare(password, hash)
},
},
},
})
Rate Limiting
Global Rate Limiting
import { rateLimit } from 'better-auth/plugins/rate-limit'
export const auth = betterAuth({
plugins: [
rateLimit({
window: 60, // 1 minute window
max: 100, // 100 requests per window
keyGenerator: (req) => {
// Rate limit by IP
return req.headers.get('x-forwarded-for') || req.ip
},
}),
],
})
Endpoint-Specific Limits
rateLimit({
endpoints: {
// Strict limits for auth endpoints
'sign-in': {
window: 300, // 5 minutes
max: 5, // 5 attempts
},
'sign-up': {
window: 3600, // 1 hour
max: 3, // 3 registrations
},
'reset-password': {
window: 3600, // 1 hour
max: 3, // 3 reset requests
},
'verify-email': {
window: 60, // 1 minute
max: 5, // 5 verification attempts
},
},
})
Progressive Delays
rateLimit({
endpoints: {
'sign-in': {
window: 300,
max: 5,
// Add delay after failed attempts
onRateLimitExceeded: async (req, res) => {
const attempts = await getFailedAttempts(req.ip)
const delay = Math.min(attempts * 1000, 30000) // Max 30s
await new Promise(resolve => setTimeout(resolve, delay))
},
},
},
})
Session Security
Secure Session Configuration
export const auth = betterAuth({
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days max
updateAge: 60 * 60 * 24, // Extend daily on activity
// Cookie settings
cookie: {
name: '__session',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
domain: process.env.COOKIE_DOMAIN,
},
// Session cache (reduce DB lookups)
cookieCache: {
enabled: true,
maxAge: 60 * 5, // 5 minute cache
},
// Require fresh session for sensitive ops
freshAge: 60 * 10, // 10 minutes
},
})
Session Invalidation
import { signOut, useSession } from '@/auth/client'
// Sign out from current device
await signOut()
// Sign out from all devices
await signOut({ revokeAllSessions: true })
// Server-side: Invalidate specific session
await auth.api.invalidateSession({ sessionId })
// Server-side: Invalidate all user sessions
await auth.api.invalidateUserSessions({ userId })
Session Binding
// Bind session to device fingerprint
export const auth = betterAuth({
session: {
// Store device info
onSessionCreated: async (session, user, request) => {
await prisma.session.update({
where: { id: session.id },
data: {
userAgent: request.headers.get('user-agent'),
ipAddress: request.ip,
},
})
},
// Validate on each request
onSessionValidate: async (session, request) => {
const storedIp = session.ipAddress
const currentIp = request.ip
// Warn on IP change (but don't block for mobile users)
if (storedIp !== currentIp) {
await logSecurityEvent('session_ip_change', {
sessionId: session.id,
oldIp: storedIp,
newIp: currentIp,
})
}
return true
},
},
})
CSRF Protection
Token-Based CSRF
export const auth = betterAuth({
csrf: {
enabled: true,
// Double submit cookie pattern
cookieName: '__csrf',
headerName: 'x-csrf-token',
// Token rotation
rotateOnAuthentication: true,
},
})
// Client: Include CSRF token
const csrfToken = getCookie('__csrf')
await fetch('/api/auth/sign-out', {
method: 'POST',
headers: {
'x-csrf-token': csrfToken,
},
})
SameSite Cookie Protection
session: {
cookie: {
sameSite: 'strict', // Strictest CSRF protection
// Or 'lax' for balance between security and usability
},
}
Security Headers
Recommended Headers
// Middleware or server config
const securityHeaders = {
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'",
].join('; '),
}
Account Security
Account Lockout
export const auth = betterAuth({
emailAndPassword: {
lockout: {
enabled: true,
maxAttempts: 5,
lockoutDuration: 15 * 60, // 15 minutes
// Notify user
onLockout: async (user) => {
await sendEmail({
to: user.email,
subject: 'Account Locked',
html: 'Your account has been locked due to multiple failed login attempts.',
})
},
},
},
})
Email Verification
export const auth = betterAuth({
emailAndPassword: {
requireEmailVerification: true,
verificationTokenExpiry: 60 * 60 * 24, // 24 hours
sendVerificationEmail: async (user, token, url) => {
await sendEmail({
to: user.email,
subject: 'Verify your email',
html: `<a href="${url}">Verify email</a>`,
})
},
},
})
Password Reset Security
export const auth = betterAuth({
emailAndPassword: {
resetPasswordTokenExpiry: 60 * 60, // 1 hour
sendResetPasswordToken: async (user, token, url) => {
await sendEmail({
to: user.email,
subject: 'Reset your password',
html: `<a href="${url}">Reset password</a>`,
})
// Log for security audit
await logSecurityEvent('password_reset_requested', {
userId: user.id,
email: user.email,
})
},
onPasswordReset: async (user) => {
// Invalidate all existing sessions
await auth.api.invalidateUserSessions({ userId: user.id })
// Notify user
await sendEmail({
to: user.email,
subject: 'Password changed',
html: 'Your password was recently changed.',
})
},
},
})
Security Logging
Audit Trail
export const auth = betterAuth({
advanced: {
hooks: {
onSignIn: async (user, session) => {
await logSecurityEvent('sign_in', {
userId: user.id,
sessionId: session.id,
method: session.method, // 'email', 'google', etc.
})
},
onSignOut: async (user, session) => {
await logSecurityEvent('sign_out', {
userId: user.id,
sessionId: session.id,
})
},
onSignUp: async (user) => {
await logSecurityEvent('sign_up', {
userId: user.id,
email: user.email,
})
},
},
},
})
async function logSecurityEvent(event: string, data: Record<string, any>) {
await prisma.securityLog.create({
data: {
event,
data,
timestamp: new Date(),
ip: data.ip,
userAgent: data.userAgent,
},
})
}
Security Checklist
- Password minimum 12 characters with complexity
- Rate limiting on all auth endpoints
- Session cookies are httpOnly and secure
- CSRF protection enabled
- Email verification required
- Account lockout after failed attempts
- Security headers configured
- Password changes invalidate sessions
- Security events logged
- 2FA available for users
Anti-Patterns
// ❌ WRONG: Weak password policy
password: { minLength: 6 }
// ✅ CORRECT: Strong policy
password: { minLength: 12, requireUppercase: true, ... }
// ❌ WRONG: No rate limiting
emailAndPassword: { enabled: true }
// ✅ CORRECT: With rate limiting
plugins: [rateLimit({ ... })]
// ❌ WRONG: Long-lived sessions
session: { expiresIn: 60 * 60 * 24 * 365 } // 1 year
// ✅ CORRECT: Reasonable expiry
session: { expiresIn: 60 * 60 * 24 * 7 } // 7 days
// ❌ WRONG: Cookies without flags
cookie: { name: 'session' }
// ✅ CORRECT: Secure cookie flags
cookie: { httpOnly: true, secure: true, sameSite: 'lax' }