Key Takeaways
- Never trust user input - validate everything
- Use parameterized queries - prevent injection
- Encode output - context-aware escaping
- Fail securely - don't leak information
Contents
1. Secure Coding Principles
- Defense in depth: Multiple layers of security
- Least privilege: Minimum necessary permissions
- Fail secure: Deny by default on errors
- Don't trust user input: Validate and sanitize
- Keep it simple: Complexity breeds vulnerabilities
2. Input Validation
Insecure
// SQL Injection vulnerable
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
mysqli_query($conn, $query);
// Direct use of user input
echo "Hello, " . $_GET['name'];
Secure
// Parameterized query
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
// Validate and sanitize
$name = filter_input(INPUT_GET, 'name', FILTER_SANITIZE_STRING);
echo "Hello, " . htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
Validation Rules
- Whitelist over blacklist
- Validate type, length, format, range
- Reject invalid input, don't try to fix it
- Validate on the server side (client-side is UX only)
3. Output Encoding
// Context-aware encoding
// HTML context
htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
// JavaScript context
json_encode($data);
// URL context
urlencode($data);
// CSS context
// Avoid user input in CSS
// Frameworks often handle this:
// React: {data} is auto-escaped
// Blade: {{ $data }} is escaped
4. Secure Authentication
# Password storage
import bcrypt
# Hash password (never store plaintext!)
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(12))
# Verify
bcrypt.checkpw(password.encode(), stored_hash)
# Session management
# - Regenerate session ID on login
# - Secure, HttpOnly, SameSite cookies
# - Implement session timeout
# - Invalidate on logout
# MFA
# Always implement for sensitive applications
5. Access Control
# IDOR Prevention
// Insecure - trusts user ID from URL
$user_id = $_GET['id'];
$data = get_user_data($user_id);
// Secure - verify ownership
$current_user = get_logged_in_user();
$data = get_user_data($user_id);
if ($data['owner_id'] !== $current_user['id']) {
die('Access denied');
}
# Principle of least privilege
# - Default deny
# - Check authorization on every request
# - Verify at the function/data level, not just UI
6. Cryptography Best Practices
- ✅ Use established libraries (OpenSSL, libsodium)
- ✅ AES-256-GCM for symmetric encryption
- ✅ Argon2id or bcrypt for passwords
- ✅ HTTPS/TLS 1.3 for transport
- ❌ Never implement your own crypto
- ❌ Never use MD5 or SHA1 for security
- ❌ Never hardcode secrets in code
7. Secure Error Handling
# Insecure - leaks information
try {
$result = $db->query($sql);
} catch (Exception $e) {
echo $e->getMessage(); // Shows SQL error to user!
}
# Secure
try {
$result = $db->query($sql);
} catch (Exception $e) {
error_log($e->getMessage()); // Log for developers
echo "An error occurred"; // Generic message for users
}
8. Security Checklist
Before Deployment
- ☐ All inputs validated server-side
- ☐ Parameterized queries (no SQL injection)
- ☐ Output encoding in all contexts
- ☐ Strong password hashing (Argon2id/bcrypt)
- ☐ Secure session management
- ☐ HTTPS enforced
- ☐ Security headers configured
- ☐ No secrets in code
- ☐ Error messages don't leak info
- ☐ Dependencies updated
FAQ
Should I use a web framework?
Yes! Modern frameworks (Django, Laravel, Rails, Spring) have built-in security features like CSRF protection, parameterized queries, and output encoding.