← Back to PHP Course | Chapter 14: Advanced PHP | Lesson 11 of 24

PHP Security Best Practices

Security best practices are the locks and habits that keep your site safe from bad guys. They include not trusting what visitors type and hiding secrets, like checking who is at the door before opening.
Syntax
php
$stmt = $pdo->prepare("SELECT * FROM table_name WHERE column = ?");   // SQL injection
$stmt->execute([$value]);

echo htmlspecialchars($input);            // XSS
$hash = password_hash($password, PASSWORD_DEFAULT);
password_verify($password, $hash);

Preventing SQL Injection

SQL injection happens when raw user input gets concatenated directly into a query string; prepared statements with bound parameters (via PDO) separate the query structure from the data, making injection structurally impossible.

Example: Preventing SQL Injection

php
<?php
// Create a new `PDO` instance with 'sqlite::memory:', stored in `$pdo`
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER, name TEXT)");
// Declare `$stmt`, set to `$pdo->prepare("INSERT INTO users (id, name) VALUES (?, ?)")`
$stmt = $pdo->prepare("INSERT INTO users (id, name) VALUES (?, ?)");
$stmt->execute([1, "Alice"]);
// Print "Injection-proof: data is bound, not concatenated" to the output
echo "Injection-proof: data is bound, not concatenated";
?>

Preventing Cross-Site Scripting (XSS)

Cross-site scripting lets an attacker inject malicious script into a page other users view; htmlspecialchars() converts characters like < and > into safe HTML entities before you echo any user-supplied text.

Example: Preventing Cross-Site Scripting (XSS)

php
<?php
// Declare `$comment`, set to "<script>alert('xss')</script>"
$comment = "<script>alert('xss')</script>";
// Print `htmlspecialchars($comment)` to the output
echo htmlspecialchars($comment);
?>

Safe Password Hashing

password_hash() applies a strong, salted one-way hash to a password before storage, and password_verify() checks a login attempt against it -- storing raw passwords should never happen under any circumstances.

Example: Safe Password Hashing

php
<?php
// Declare `$hash`, set to `password_hash("secret123", PASSWORD_DEFAULT)`
$hash = password_hash("secret123", PASSWORD_DEFAULT);
// Print a detailed dump (with types) of `password_verify("secret123", $hash)`
var_dump(password_verify("secret123", $hash));
?>

Preventing CSRF Attacks

CSRF tricks a logged-in user's browser into submitting a request they never intended; embedding a random, per-session token in every form and verifying it on submit blocks forged requests from other sites.

Example: Preventing CSRF Attacks

php
<?php
// Call `session_start()`
session_start();
// Set `$_SESSION['csrf_token']` to `bin2hex(random_bytes(16))`
$_SESSION['csrf_token'] = bin2hex(random_bytes(16));
// Print "Token embedded in form, verified with hash_equals() on submit" to the output
echo "Token embedded in form, verified with hash_equals() on submit";
?>

Validating and Sanitizing Inputs

filter_var() with constants like FILTER_SANITIZE_EMAIL or FILTER_VALIDATE_URL gives you a consistent, tested way to clean and validate common input types instead of writing fragile custom regex for each one.

Example: Validating and Sanitizing Inputs

php
<?php
// Declare `$email`, set to "[email protected]"
$email = "[email protected]";
// Print `filter_var($email, FILTER_VALIDATE_EMAIL) ? "Valid" : "Invalid"` to the output
echo filter_var($email, FILTER_VALIDATE_EMAIL) ? "Valid" : "Invalid";
?>
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Building SQL by concatenating user input, which allows SQL injection.
  2. Printing user input without htmlspecialchars, which allows cross-site scripting.
  3. Storing passwords with md5 or sha1 instead of password_hash.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.