authentication
SKILL.md
Authentication - Secure Auth Patterns
Production patterns for JWT, sessions, OAuth, password handling, and MFA
When to Use This Skill
Use this skill when:
- Implementing JWT authentication
- Building session-based auth
- Integrating OAuth providers (Google, GitHub)
- Implementing password reset flows
- Adding multi-factor authentication
- Securing API endpoints
Don't use this skill when:
- Using managed auth services (Auth0, Clerk) - follow their docs
- Building public APIs without auth
- Authorization logic (see
securityskill)
Critical Patterns
Pattern 1: Password Hashing
When: Storing user passwords securely
// ✅ GOOD: Using bcrypt or argon2
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12; // Adjust based on hardware
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// Registration
async function register(email: string, password: string) {
// Validate password strength
if (!isStrongPassword(password)) {
throw new ValidationError('Password too weak');
}
const hashedPassword = await hashPassword(password);
return db.user.create({
data: {
email,
password: hashedPassword,
},
});
}
// Login
async function login(email: string, password: string) {
const user = await db.user.findUnique({ where: { email } });
// Use constant-time comparison message
if (!user || !(await verifyPassword(password, user.password))) {
throw new UnauthorizedError('Invalid credentials'); // Same message for both!
}
return user;
}
// ❌ BAD: Storing plain passwords or using weak hashing
await db.user.create({ data: { password } }); // Plain text!
const hash = crypto.createHash('md5').update(password).digest('hex'); // MD5 is broken!
Pattern 2: JWT Authentication
When: Stateless API authentication
// ✅ GOOD: JWT with proper configuration
import jwt from 'jsonwebtoken';
interface TokenPayload {
userId: string;
email: string;
role: string;
}
const ACCESS_TOKEN_SECRET = process.env.JWT_SECRET!;
const REFRESH_TOKEN_SECRET = process.env.JWT_REFRESH_SECRET!;
const ACCESS_TOKEN_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';
function generateTokens(user: User) {
const payload: TokenPayload = {
userId: user.id,
email: user.email,
role: user.role,
};
const accessToken = jwt.sign(payload, ACCESS_TOKEN_SECRET, {
expiresIn: ACCESS_TOKEN_EXPIRY,
});
const refreshToken = jwt.sign(
{ userId: user.id },
REFRESH_TOKEN_SECRET,
{ expiresIn: REFRESH_TOKEN_EXPIRY }
);
return { accessToken, refreshToken };
}
function verifyAccessToken(token: string): TokenPayload {
try {
return jwt.verify(token, ACCESS_TOKEN_SECRET) as TokenPayload;
} catch {
throw new UnauthorizedError('Invalid or expired token');
}
}
// ✅ GOOD: Store refresh token securely
async function login(email: string, password: string) {
const user = await validateCredentials(email, password);
const { accessToken, refreshToken } = generateTokens(user);
// Store refresh token hash in database (for revocation)
await db.refreshToken.create({
data: {
userId: user.id,
tokenHash: await hashToken(refreshToken),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
},
});
return { accessToken, refreshToken };
}
// ❌ BAD: Long-lived access tokens
jwt.sign(payload, secret, { expiresIn: '30d' }); // Too long for access token!
// ❌ BAD: Storing sensitive data in JWT
jwt.sign({ password: user.password, ssn: user.ssn }, secret); // Never!
Pattern 3: Secure Cookie Sessions
When: Server-side session management
// ✅ GOOD: Secure session cookies
import { cookies } from 'next/headers';
import { SignJWT, jwtVerify } from 'jose';
const SESSION_SECRET = new TextEncoder().encode(process.env.SESSION_SECRET);
const SESSION_DURATION = 60 * 60 * 24 * 7; // 7 days
interface SessionPayload {
userId: string;
expiresAt: number;
}
async function createSession(userId: string) {
const expiresAt = Date.now() + SESSION_DURATION * 1000;
const token = await new SignJWT({ userId, expiresAt })
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime(expiresAt)
.sign(SESSION_SECRET);
(await cookies()).set('session', token, {
httpOnly: true, // Not accessible via JavaScript
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax', // CSRF protection
maxAge: SESSION_DURATION,
path: '/',
});
}
async function getSession(): Promise<SessionPayload | null> {
const token = (await cookies()).get('session')?.value;
if (!token) return null;
try {
const { payload } = await jwtVerify(token, SESSION_SECRET);
return payload as unknown as SessionPayload;
} catch {
return null;
}
}
async function deleteSession() {
(await cookies()).delete('session');
}
// ❌ BAD: Insecure cookie settings
cookies().set('session', token, {
httpOnly: false, // XSS vulnerable!
secure: false, // Sent over HTTP!
sameSite: 'none', // CSRF vulnerable without secure!
});
Pattern 4: OAuth Integration
When: Allowing sign-in with external providers
// ✅ GOOD: OAuth 2.0 flow with PKCE
// Step 1: Redirect to provider
async function initiateOAuth(provider: 'google' | 'github') {
const state = generateSecureRandom(32);
const codeVerifier = generateSecureRandom(64);
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store state and verifier in session
await setOAuthSession({ state, codeVerifier });
const authUrl = new URL(OAUTH_ENDPOINTS[provider].authorize);
authUrl.searchParams.set('client_id', OAUTH_CONFIG[provider].clientId);
authUrl.searchParams.set('redirect_uri', OAUTH_CONFIG[provider].redirectUri);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', OAUTH_CONFIG[provider].scopes);
authUrl.searchParams.set('state', state);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
return authUrl.toString();
}
// Step 2: Handle callback
async function handleOAuthCallback(code: string, state: string) {
const session = await getOAuthSession();
// Verify state to prevent CSRF
if (state !== session.state) {
throw new UnauthorizedError('Invalid state parameter');
}
// Exchange code for tokens
const tokens = await exchangeCodeForTokens(code, session.codeVerifier);
// Get user info from provider
const providerUser = await getProviderUserInfo(tokens.accessToken);
// Find or create user
let user = await db.user.findUnique({
where: { email: providerUser.email },
});
if (!user) {
user = await db.user.create({
data: {
email: providerUser.email,
name: providerUser.name,
emailVerified: true, // OAuth emails are verified
},
});
}
// Create session
await createSession(user.id);
return user;
}
// ❌ BAD: Not validating state parameter
async function handleCallback(code: string) {
const tokens = await exchangeCode(code); // CSRF attack possible!
}
Pattern 5: Password Reset Flow
When: Allowing users to reset forgotten passwords
// ✅ GOOD: Secure password reset
import crypto from 'crypto';
async function requestPasswordReset(email: string) {
const user = await db.user.findUnique({ where: { email } });
// Always respond the same way (prevent email enumeration)
if (!user) {
// Log attempt but don't reveal if email exists
logger.info('Password reset requested for unknown email');
return; // Same response as success
}
// Generate secure token
const token = crypto.randomBytes(32).toString('hex');
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
// Store hashed token with expiry
await db.passwordReset.create({
data: {
userId: user.id,
tokenHash,
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
},
});
// Send email with unhashed token
await sendEmail({
to: user.email,
template: 'password-reset',
data: {
resetUrl: `${process.env.APP_URL}/reset-password?token=${token}`,
},
});
}
async function resetPassword(token: string, newPassword: string) {
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
const reset = await db.passwordReset.findFirst({
where: {
tokenHash,
expiresAt: { gt: new Date() },
usedAt: null,
},
include: { user: true },
});
if (!reset) {
throw new UnauthorizedError('Invalid or expired reset token');
}
// Update password and mark token as used
await db.$transaction([
db.user.update({
where: { id: reset.userId },
data: { password: await hashPassword(newPassword) },
}),
db.passwordReset.update({
where: { id: reset.id },
data: { usedAt: new Date() },
}),
// Invalidate all sessions (logout everywhere)
db.session.deleteMany({ where: { userId: reset.userId } }),
]);
}
// ❌ BAD: Predictable tokens or long expiry
const token = `reset-${user.id}-${Date.now()}`; // Predictable!
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days is too long!
Code Examples
For complete, production-ready examples, see references/examples.md:
- Auth Middleware with JWT
- Rate-Limited Login (brute force protection)
- MFA with TOTP
- Refresh Token Rotation
Anti-Patterns
Don't: Reveal User Existence
// ❌ BAD: Different messages reveal if email exists
if (!user) throw new Error('Email not found');
if (!passwordMatch) throw new Error('Wrong password');
// ✅ GOOD: Same message for both
if (!user || !passwordMatch) {
throw new UnauthorizedError('Invalid credentials');
}
Don't: Store Tokens in localStorage
// ❌ BAD: XSS vulnerable
localStorage.setItem('token', accessToken);
// ✅ GOOD: httpOnly cookie (for sessions)
// Or: Keep access token in memory, refresh token in httpOnly cookie
Don't: Skip Token Revocation
// ❌ BAD: No way to logout from all devices
function logout() {
// Just delete local token - other sessions still valid!
}
// ✅ GOOD: Track sessions server-side
async function logout(userId: string) {
await db.session.deleteMany({ where: { userId } });
}
Quick Reference
| Scenario | Pattern | Key Points |
|---|---|---|
| Password storage | bcrypt/argon2 | 12+ salt rounds |
| API auth | JWT | Short-lived access tokens |
| Web sessions | httpOnly cookies | Secure, SameSite |
| Third-party login | OAuth 2.0 + PKCE | Validate state |
| Password reset | Hashed tokens | 1 hour expiry |
| Brute force | Rate limiting | By IP + email |
Resources
Related Skills:
- security: OWASP, input validation
- caching: Session storage
- api-design: Protected endpoints
Keywords
authentication, jwt, sessions, oauth, mfa, totp, password-hashing, bcrypt, login, logout, refresh-token
Weekly Installs
2
Repository
dsantiagomj/dsm…-toolkitFirst Seen
Feb 25, 2026
Security Audits
Installed on
trae-cn2
codebuddy2
github-copilot2
codex2
kiro-cli2
kimi-cli2