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
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!