auth-security-reviewer
SKILL.md
Auth Security Reviewer
Comprehensive security review of authentication systems.
Session Security Checklist
// ❌ INSECURE Session Configuration
app.use(
session({
secret: "weak-secret", // Too simple
resave: true, // Unnecessary
saveUninitialized: true, // Creates unnecessary sessions
cookie: {
secure: false, // Not HTTPS-only
httpOnly: false, // Accessible via JavaScript
sameSite: false, // CSRF vulnerable
maxAge: 365 * 24 * 60 * 60 * 1000, // 1 year - too long
},
})
);
// ✅ SECURE Session Configuration
app.use(
session({
secret: process.env.SESSION_SECRET, // From environment
resave: false,
saveUninitialized: false,
name: "sessionId", // Don't use default 'connect.sid'
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
sameSite: "strict", // CSRF protection
maxAge: 24 * 60 * 60 * 1000, // 24 hours
domain: process.env.COOKIE_DOMAIN,
},
store: new RedisStore({
client: redisClient,
ttl: 86400,
}),
})
);
JWT Security Review
// ❌ INSECURE JWT Implementation
const token = jwt.sign(
{ userId: user.id },
"weak-secret", // Hardcoded secret
{ algorithm: "HS256" } // No expiration
);
// Store in localStorage
localStorage.setItem("token", token); // XSS vulnerable
// ✅ SECURE JWT Implementation
const token = jwt.sign(
{
userId: user.id,
role: user.role,
iat: Math.floor(Date.now() / 1000),
},
process.env.JWT_SECRET, // Strong secret from env
{
algorithm: "HS256",
expiresIn: "15m", // Short-lived
issuer: "myapp.com",
audience: "myapp.com",
}
);
// Store in httpOnly cookie
res.cookie("accessToken", token, {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 15 * 60 * 1000,
});
// Refresh token with longer expiry
const refreshToken = jwt.sign(
{ userId: user.id, type: "refresh" },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: "7d" }
);
// Store refresh token in database
await storeRefreshToken(user.id, refreshToken);
CSRF Protection
// Using csurf middleware
import csrf from "csurf";
const csrfProtection = csrf({ cookie: true });
// Apply to state-changing routes
app.post("/api/transfer", csrfProtection, async (req, res) => {
// Protected from CSRF
await processTransfer(req.body);
res.json({ success: true });
});
// Provide CSRF token to frontend
app.get("/api/csrf-token", csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
// Frontend usage
const csrfToken = await fetch("/api/csrf-token").then((r) => r.json());
await fetch("/api/transfer", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken.csrfToken,
},
body: JSON.stringify({ amount: 100 }),
});
Password Security
// ❌ INSECURE Password Handling
const password = req.body.password;
const hash = crypto.createHash("md5").update(password).digest("hex"); // MD5 is broken
await db.user.create({ password: hash });
// ✅ SECURE Password Handling
import bcrypt from "bcrypt";
// Hashing
const saltRounds = 12; // Adjust based on security requirements
const hash = await bcrypt.hash(password, saltRounds);
await db.user.create({ passwordHash: hash });
// Verification
const isValid = await bcrypt.compare(password, user.passwordHash);
// Password requirements
function validatePassword(password: string): boolean {
return (
password.length >= 12 &&
/[A-Z]/.test(password) && // Uppercase
/[a-z]/.test(password) && // Lowercase
/[0-9]/.test(password) && // Number
/[^A-Za-z0-9]/.test(password) // Special char
);
}
// Check against breached passwords
import { pwnedPassword } from "hibp";
const breachCount = await pwnedPassword(password);
if (breachCount > 0) {
throw new Error("This password has been found in data breaches");
}
Multi-Factor Authentication
// TOTP-based MFA
import speakeasy from "speakeasy";
import qrcode from "qrcode";
// Generate secret
const secret = speakeasy.generateSecret({
name: `MyApp (${user.email})`,
issuer: "MyApp",
});
// Store secret
await db.user.update({
where: { id: user.id },
data: {
mfaSecret: secret.base32,
mfaEnabled: false, // Not enabled until verified
},
});
// Generate QR code
const qrCodeUrl = await qrcode.toDataURL(secret.otpauth_url);
// Verify TOTP token
function verifyMFA(token: string, secret: string): boolean {
return speakeasy.totp.verify({
secret,
encoding: "base32",
token,
window: 2, // Allow 2 time steps before/after
});
}
// Backup codes
function generateBackupCodes(): string[] {
return Array.from({ length: 10 }, () =>
crypto.randomBytes(4).toString("hex").toUpperCase()
);
}
Authorization Vulnerabilities
// ❌ INSECURE: Missing authorization check
app.get("/api/users/:id/profile", async (req, res) => {
const profile = await db.user.findUnique({
where: { id: req.params.id },
});
res.json(profile); // Anyone can access any profile!
});
// ✅ SECURE: Proper authorization
app.get("/api/users/:id/profile", authenticate, async (req, res) => {
// Check if user can access this profile
if (req.user.id !== req.params.id && req.user.role !== "ADMIN") {
return res.status(403).json({ error: "Forbidden" });
}
const profile = await db.user.findUnique({
where: { id: req.params.id },
});
res.json(profile);
});
// ❌ INSECURE: IDOR vulnerability
app.delete("/api/orders/:id", async (req, res) => {
await db.order.delete({ where: { id: req.params.id } });
res.json({ success: true });
});
// ✅ SECURE: Verify ownership
app.delete("/api/orders/:id", authenticate, async (req, res) => {
const order = await db.order.findUnique({
where: { id: req.params.id },
});
if (!order) {
return res.status(404).json({ error: "Not found" });
}
if (order.userId !== req.user.id) {
return res.status(403).json({ error: "Forbidden" });
}
await db.order.delete({ where: { id: req.params.id } });
res.json({ success: true });
});
Session Fixation Prevention
// ❌ INSECURE: Session not regenerated on login
app.post("/login", async (req, res) => {
const user = await authenticate(req.body);
req.session.userId = user.id;
res.json({ success: true });
});
// ✅ SECURE: Regenerate session on login
app.post("/login", async (req, res) => {
const user = await authenticate(req.body);
// Regenerate session to prevent fixation
req.session.regenerate((err) => {
if (err) return res.status(500).json({ error: "Server error" });
req.session.userId = user.id;
res.json({ success: true });
});
});
// Also regenerate on privilege escalation
app.post("/admin/elevate", async (req, res) => {
// Verify admin credentials
await verifyAdminPassword(req.body.password);
// Regenerate session
req.session.regenerate((err) => {
if (err) return res.status(500).json({ error: "Server error" });
req.session.isAdmin = true;
res.json({ success: true });
});
});
Rate Limiting on Auth Endpoints
import rateLimit from "express-rate-limit";
// Strict rate limit for login
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: "Too many login attempts, please try again later",
standardHeaders: true,
legacyHeaders: false,
// Use IP + username for more granular limiting
keyGenerator: (req) => `${req.ip}-${req.body.email}`,
});
app.post("/api/login", loginLimiter, async (req, res) => {
// Login logic
});
// Even stricter for password reset
const resetLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 3,
message: "Too many password reset attempts",
});
app.post("/api/password-reset", resetLimiter, async (req, res) => {
// Password reset logic
});
Security Testing
// tests/auth-security.test.ts
describe("Auth Security", () => {
describe("Session Security", () => {
it("should set httpOnly cookie", async () => {
const response = await request(app)
.post("/api/login")
.send({ email: "test@example.com", password: "password123" });
const cookie = response.headers["set-cookie"][0];
expect(cookie).toContain("HttpOnly");
expect(cookie).toContain("Secure");
expect(cookie).toContain("SameSite=Strict");
});
it("should regenerate session on login", async () => {
const agent = request.agent(app);
// Get initial session
await agent.get("/");
const initialCookie = agent.jar.getCookie("sessionId");
// Login
await agent.post("/api/login").send({
email: "test@example.com",
password: "password123",
});
const loginCookie = agent.jar.getCookie("sessionId");
// Session ID should change
expect(loginCookie.value).not.toBe(initialCookie.value);
});
});
describe("CSRF Protection", () => {
it("should reject requests without CSRF token", async () => {
await request(app)
.post("/api/transfer")
.send({ amount: 100 })
.expect(403);
});
it("should accept requests with valid CSRF token", async () => {
const { csrfToken } = await request(app)
.get("/api/csrf-token")
.then((r) => r.body);
await request(app)
.post("/api/transfer")
.set("X-CSRF-Token", csrfToken)
.send({ amount: 100 })
.expect(200);
});
});
describe("Authorization", () => {
it("should prevent IDOR attacks", async () => {
const user1 = await createUser();
const user2 = await createUser();
const token1 = generateToken(user1);
// Try to access user2's profile with user1's token
await request(app)
.get(`/api/users/${user2.id}/profile`)
.set("Authorization", `Bearer ${token1}`)
.expect(403);
});
});
});
Best Practices
- Regenerate sessions: On login and privilege changes
- Short-lived tokens: 15min access, 7-day refresh
- CSRF protection: All state-changing operations
- Rate limiting: Prevent brute force
- Secure cookies: HttpOnly, Secure, SameSite
- MFA: For sensitive operations
- Audit logs: Track authentication events
Output Checklist
- Session configuration reviewed
- JWT implementation secured
- CSRF protection enabled
- Password hashing with bcrypt
- MFA implementation (if required)
- Authorization checks on all endpoints
- Session fixation prevention
- Rate limiting on auth endpoints
- Security tests written
- Audit logging configured
Weekly Installs
10
Repository
patricio0312rev/skillsFirst Seen
10 days ago
Installed on
claude-code8
gemini-cli7
antigravity7
windsurf7
github-copilot7
codex7