Back to Blog
Security

Modern PHP Security: Beyond the Basics

Deep dive into advanced security techniques including CSP headers, rate limiting, and secure session management for production applications.

James Wilson
January 3, 2026
15 min read

Modern PHP Security: Beyond the Basics

Security is paramount in modern web applications. Let's explore advanced security techniques beyond basic SQL injection prevention.

Content Security Policy (CSP)

CSP helps prevent XSS attacks by controlling which resources can be loaded:

php
1header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");

Better CSP Implementation

php
1class SecurityHeaders
2{
3    public static function apply(): void
4    {
5        header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'");
6        header("X-Frame-Options: DENY");
7        header("X-Content-Type-Options: nosniff");
8        header("X-XSS-Protection: 1; mode=block");
9        header("Referrer-Policy: strict-origin-when-cross-origin");
10        header("Permissions-Policy: geolocation=(), microphone=(), camera=()");
11    }
12}

Rate Limiting

Prevent brute force attacks with rate limiting:

php
1class RateLimiter
2{
3    private Redis $redis;
4    
5    public function __construct(Redis $redis)
6    {
7        $this->redis = $redis;
8    }
9    
10    public function attempt(string $key, int $maxAttempts, int $decaySeconds): bool
11    {
12        $attempts = (int) $this->redis->get($key) ?: 0;
13        
14        if ($attempts >= $maxAttempts) {
15            return false;
16        }
17        
18        $this->redis->incr($key);
19        $this->redis->expire($key, $decaySeconds);
20        
21        return true;
22    }
23    
24    public function clear(string $key): void
25    {
26        $this->redis->del($key);
27    }
28}
29
30// Usage
31$limiter = new RateLimiter($redis);
32$ip = $_SERVER['REMOTE_ADDR'];
33
34if (!$limiter->attempt("login:$ip", 5, 300)) {
35    http_response_code(429);
36    die('Too many login attempts. Try again in 5 minutes.');
37}

Secure Session Management

php
1class SecureSession
2{
3    public static function start(): void
4    {
5        ini_set('session.cookie_httponly', 1);
6        ini_set('session.cookie_secure', 1);
7        ini_set('session.cookie_samesite', 'Strict');
8        ini_set('session.use_strict_mode', 1);
9        
10        session_start();
11        
12        // Regenerate session ID periodically
13        if (!isset($_SESSION['created'])) {
14            $_SESSION['created'] = time();
15        } elseif (time() - $_SESSION['created'] > 1800) {
16            session_regenerate_id(true);
17            $_SESSION['created'] = time();
18        }
19    }
20    
21    public static function set(string $key, mixed $value): void
22    {
23        $_SESSION[$key] = $value;
24    }
25    
26    public static function get(string $key): mixed
27    {
28        return $_SESSION[$key] ?? null;
29    }
30    
31    public static function destroy(): void
32    {
33        $_SESSION = [];
34        session_destroy();
35    }
36}

CSRF Protection

php
1class CSRF
2{
3    public static function generateToken(): string
4    {
5        if (!isset($_SESSION['csrf_token'])) {
6            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
7        }
8        return $_SESSION['csrf_token'];
9    }
10    
11    public static function validateToken(string $token): bool
12    {
13        return isset($_SESSION['csrf_token']) && 
14               hash_equals($_SESSION['csrf_token'], $token);
15    }
16    
17    public static function field(): string
18    {
19        $token = self::generateToken();
20        return "<input type='hidden' name='csrf_token' value='$token'>";
21    }
22}
23
24// In your form
25echo CSRF::field();
26
27// On form submission
28if (!CSRF::validateToken($_POST['csrf_token'] ?? '')) {
29    die('CSRF token validation failed');
30}

Password Security

php
1class PasswordManager
2{
3    public static function hash(string $password): string
4    {
5        return password_hash($password, PASSWORD_ARGON2ID, [
6            'memory_cost' => 65536,
7            'time_cost' => 4,
8            'threads' => 3
9        ]);
10    }
11    
12    public static function verify(string $password, string $hash): bool
13    {
14        return password_verify($password, $hash);
15    }
16    
17    public static function needsRehash(string $hash): bool
18    {
19        return password_needs_rehash($hash, PASSWORD_ARGON2ID);
20    }
21    
22    public static function validateStrength(string $password): array
23    {
24        $errors = [];
25        
26        if (strlen($password) < 12) {
27            $errors[] = 'Password must be at least 12 characters';
28        }
29        if (!preg_match('/[A-Z]/', $password)) {
30            $errors[] = 'Password must contain uppercase letter';
31        }
32        if (!preg_match('/[a-z]/', $password)) {
33            $errors[] = 'Password must contain lowercase letter';
34        }
35        if (!preg_match('/[0-9]/', $password)) {
36            $errors[] = 'Password must contain number';
37        }
38        if (!preg_match('/[^A-Za-z0-9]/', $password)) {
39            $errors[] = 'Password must contain special character';
40        }
41        
42        return $errors;
43    }
44}

Input Sanitization

php
1class InputSanitizer
2{
3    public static function sanitizeString(string $input): string
4    {
5        return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
6    }
7    
8    public static function sanitizeEmail(string $email): ?string
9    {
10        $email = filter_var($email, FILTER_SANITIZE_EMAIL);
11        return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : null;
12    }
13    
14    public static function sanitizeUrl(string $url): ?string
15    {
16        $url = filter_var($url, FILTER_SANITIZE_URL);
17        return filter_var($url, FILTER_VALIDATE_URL) ? $url : null;
18    }
19    
20    public static function sanitizeInt(mixed $value): ?int
21    {
22        return filter_var($value, FILTER_VALIDATE_INT) !== false 
23            ? (int) $value 
24            : null;
25    }
26}

File Upload Security

php
1class SecureFileUpload
2{
3    private const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
4    private const MAX_SIZE = 5242880; // 5MB
5    
6    public static function validate(array $file): array
7    {
8        $errors = [];
9        
10        if ($file['error'] !== UPLOAD_ERR_OK) {
11            $errors[] = 'Upload failed';
12            return $errors;
13        }
14        
15        if ($file['size'] > self::MAX_SIZE) {
16            $errors[] = 'File too large (max 5MB)';
17        }
18        
19        $finfo = finfo_open(FILEINFO_MIME_TYPE);
20        $mimeType = finfo_file($finfo, $file['tmp_name']);
21        finfo_close($finfo);
22        
23        if (!in_array($mimeType, self::ALLOWED_TYPES)) {
24            $errors[] = 'Invalid file type';
25        }
26        
27        return $errors;
28    }
29    
30    public static function save(array $file, string $directory): string
31    {
32        $errors = self::validate($file);
33        if (!empty($errors)) {
34            throw new InvalidArgumentException(implode(', ', $errors));
35        }
36        
37        $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
38        $filename = bin2hex(random_bytes(16)) . '.' . $extension;
39        $destination = $directory . '/' . $filename;
40        
41        if (!move_uploaded_file($file['tmp_name'], $destination)) {
42            throw new RuntimeException('Failed to save file');
43        }
44        
45        return $filename;
46    }
47}

Security Checklist

  • ✅ Use HTTPS everywhere
  • ✅ Implement CSP headers
  • ✅ Enable rate limiting
  • ✅ Use secure session configuration
  • ✅ Implement CSRF protection
  • ✅ Hash passwords with Argon2id
  • ✅ Sanitize all user input
  • ✅ Validate file uploads
  • ✅ Keep dependencies updated
  • ✅ Use security headers
  • Conclusion

    Security is an ongoing process. Implement these techniques, stay updated on vulnerabilities, and always assume user input is malicious!

    Enjoyed this article?

    Share it with your network!