| name | Auth Security Patterns |
| description | Auto-enforce authentication security best practices. Activates when implementing password policies, rate limiting, session security, CSRF protection, or security headers in auth flows. |
| version | 1.0.0 |
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,
validate: (password) => {
if (commonPasswords.includes(password.toLowerCase())) {
return 'Password is too common'
}
if (/(.)\1{2,}/.test(password)) {
return 'Password cannot have 3+ repeated characters'
}
return true
},
},
},
})
Password Hashing
export const auth = betterAuth({
advanced: {
password: {
hash: async (password) => {
const salt = await bcrypt.genSalt(12)
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,
max: 100,
keyGenerator: (req) => {
return req.headers.get('x-forwarded-for') || req.ip
},
}),
],
})
Endpoint-Specific Limits
rateLimit({
endpoints: {
'sign-in': {
window: 300,
max: 5,
},
'sign-up': {
window: 3600,
max: 3,
},
'reset-password': {
window: 3600,
max: 3,
},
'verify-email': {
window: 60,
max: 5,
},
},
})
Progressive Delays
rateLimit({
endpoints: {
'sign-in': {
window: 300,
max: 5,
onRateLimitExceeded: async (req, res) => {
const attempts = await getFailedAttempts(req.ip)
const delay = Math.min(attempts * 1000, 30000)
await new Promise(resolve => setTimeout(resolve, delay))
},
},
},
})
Session Security
Secure Session Configuration
export const auth = betterAuth({
session: {
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
cookie: {
name: '__session',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
domain: process.env.COOKIE_DOMAIN,
},
cookieCache: {
enabled: true,
maxAge: 60 * 5,
},
freshAge: 60 * 10,
},
})
Session Invalidation
import { signOut, useSession } from '@/auth/client'
await signOut()
await signOut({ revokeAllSessions: true })
await auth.api.invalidateSession({ sessionId })
await auth.api.invalidateUserSessions({ userId })
Session Binding
export const auth = betterAuth({
session: {
onSessionCreated: async (session, user, request) => {
await prisma.session.update({
where: { id: session.id },
data: {
userAgent: request.headers.get('user-agent'),
ipAddress: request.ip,
},
})
},
onSessionValidate: async (session, request) => {
const storedIp = session.ipAddress
const currentIp = request.ip
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,
cookieName: '__csrf',
headerName: 'x-csrf-token',
rotateOnAuthentication: true,
},
})
const csrfToken = getCookie('__csrf')
await fetch('/api/auth/sign-out', {
method: 'POST',
headers: {
'x-csrf-token': csrfToken,
},
})
SameSite Cookie Protection
session: {
cookie: {
sameSite: 'strict',
},
}
Security Headers
Recommended Headers
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,
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,
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,
sendResetPasswordToken: async (user, token, url) => {
await sendEmail({
to: user.email,
subject: 'Reset your password',
html: `<a href="${url}">Reset password</a>`,
})
await logSecurityEvent('password_reset_requested', {
userId: user.id,
email: user.email,
})
},
onPasswordReset: async (user) => {
await auth.api.invalidateUserSessions({ userId: user.id })
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,
})
},
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
Anti-Patterns
password: { minLength: 6 }
password: { minLength: 12, requireUppercase: true, ... }
emailAndPassword: { enabled: true }
plugins: [rateLimit({ ... })]
session: { expiresIn: 60 * 60 * 24 * 365 }
session: { expiresIn: 60 * 60 * 24 * 7 }
cookie: { name: 'session' }
cookie: { httpOnly: true, secure: true, sameSite: 'lax' }