Milind Daraniya

OWASP in PHP: Simple Guide to Write Safer Code with Real Examples

Published June 11th, 2026 11 min read

When I build PHP applications, I do not only think about features. I also think about security from the beginning. That is where OWASP becomes very important. The OWASP Foundation is a nonprofit foundation focused on improving software security, and its work includes community-led open source projects, chapters, members, and conferences.

OWASP Top 10 is one of the most important awareness documents for web developers. OWASP describes it as a standard awareness document for developers and web application security, based on the most critical security risks to web applications. The latest released version is OWASP Top 10: 2025.

OWASP also maintains the Cheat Sheet Series, which is a concise collection of high-value guidance on specific application security topics written by security professionals. For a developer, this is one of the most practical places to learn secure coding habits.


What is OWASP?

OWASP stands for Open Worldwide Application Security Project. In simple English, it is a community that helps developers build safer software. Its main purpose is not to sell a product or force one technology. It gives practical guidance, standards, and learning material that developers can use in real projects.

If I explain OWASP in one line, I would say this:

OWASP helps us write code in a way that is harder to break.

That is the real value for PHP developers, students, and junior developers.


Why OWASP matters for PHP developers

PHP applications often handle the most sensitive parts of a system:

  • login and logout
  • user sessions
  • forms
  • database access
  • file uploads
  • payment data
  • admin access

That means small mistakes can create serious security problems. OWASP’s guidance covers the most common web risks, including injection, XSS, CSRF, broken access control, authentication, and password storage.

As a senior developer, I look at OWASP as a checklist for safe thinking. It helps me ask the right questions before I ship code.


Main OWASP ideas every PHP developer should know

1) Injection

Injection happens when untrusted input is sent into a command or query in a way that changes the meaning of that command. OWASP’s SQL Injection Prevention guidance says parameterized queries are the best defense against SQL injection.

In PHP, this usually means: do not build SQL by joining raw user input into a string.


2) Cross-Site Scripting (XSS)

OWASP says the XSS Prevention Cheat Sheet is meant to help developers prevent XSS vulnerabilities. XSS can lead to account impersonation, observing user behavior, loading external content, and stealing sensitive data.

In PHP, this usually means: escape output before showing user data in HTML.


3) Cross-Site Request Forgery (CSRF)

OWASP explains that CSRF happens when a malicious site tricks an authenticated user’s browser into performing an unwanted action on a trusted site.

In PHP, this usually means: add a CSRF token to forms and verify it before processing the request.


4) Broken Access Control

OWASP’s Authorization Cheat Sheet focuses on implementing authorization logic that is robust, maintainable, and scalable. Broken access control happens when users can do things they should not be able to do.

In PHP, this usually means: check the current user’s role and permission before allowing sensitive actions.


5) Password Storage

OWASP’s Password Storage Cheat Sheet says passwords must be protected even if the application or database is compromised.

In PHP, this usually means: never store plain text passwords. Use proper password hashing.


6) Authentication

OWASP defines authentication as the process of verifying that an individual, entity, or website is who or what it claims to be. It also has guidance for MFA and credential-stuffing defense.

In PHP, this usually means: secure login, strong password handling, and better protection for account access.


Real PHP Example: Secure Profile Update Demo

This example is written in one file so a junior developer can run it and understand the idea directly. In a real project, I would split it into controller, service, repository, and view files. The goal here is learning OWASP concepts clearly. The example demonstrates safe database queries, output encoding, CSRF protection, basic authorization, and secure password storage practices. These ideas match OWASP guidance on injection prevention, XSS prevention, CSRF defense, authorization, and password storage.

 
<?php
declare(strict_types=1);

session_start();

/*
|--------------------------------------------------------------------------
| OWASP Secure Demo
|--------------------------------------------------------------------------
| This single file demonstrates:
| - Prepared statements to avoid SQL Injection
| - htmlspecialchars() to reduce XSS risk in output
| - CSRF token protection for form submission
| - Basic authorization check for sensitive actions
| - password_hash() / password_verify() for password storage
|
| In a real project, split this into multiple files.
*/

// -----------------------------
// 1) Database setup
// -----------------------------
$dbFile = __DIR__ . '/owasp_demo.sqlite';

$pdo = new PDO('sqlite:' . $dbFile);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Create a simple users table if it does not exist
$pdo->exec("
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT NOT NULL UNIQUE,
        password TEXT NOT NULL,
        role TEXT NOT NULL DEFAULT 'user',
        bio TEXT DEFAULT ''
    )
");

// Seed one demo user if database is empty
$count = (int) $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();

if ($count === 0) {
    $stmt = $pdo->prepare("INSERT INTO users (name, email, password, role, bio) VALUES (?, ?, ?, ?, ?)");
    $stmt->execute([
        'Admin User',
        'admin@example.com',
        password_hash('Admin@123', PASSWORD_DEFAULT),
        'admin',
        'This is <b>admin</b> bio.'
    ]);
}

// -----------------------------
// 2) CSRF token setup
// -----------------------------
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// -----------------------------
// 3) Simulated login
// -----------------------------
// For demo purpose, we auto-login the first user.
// In real life, this should come from a login form and password verification.
if (empty($_SESSION['user_id'])) {
    $user = $pdo->query("SELECT id, name, email, role, bio FROM users ORDER BY id ASC LIMIT 1")->fetch(PDO::FETCH_ASSOC);
    $_SESSION['user_id'] = (int) $user['id'];
    $_SESSION['user_role'] = $user['role'];
    $_SESSION['user_name'] = $user['name'];
}

// -----------------------------
// 4) Handle form submission
// -----------------------------
$message = '';
$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // CSRF validation
    $token = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_token'], $token)) {
        $error = 'Invalid CSRF token.';
    } else {
        // Basic authorization check
        if (empty($_SESSION['user_id'])) {
            $error = 'You must be logged in to update profile.';
        } else {
            $name = trim($_POST['name'] ?? '');
            $bio = trim($_POST['bio'] ?? '');
            $newPassword = trim($_POST['password'] ?? '');

            if ($name === '') {
                $error = 'Name is required.';
            } else {
                // Prepared statement prevents SQL injection
                $stmt = $pdo->prepare("UPDATE users SET name = ?, bio = ? WHERE id = ?");
                $stmt->execute([
                    $name,
                    $bio,
                    $_SESSION['user_id']
                ]);

                // Optional password update
                if ($newPassword !== '') {
                    if (strlen($newPassword) < 8) {
                        $error = 'Password must be at least 8 characters.';
                    } else {
                        $hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);

                        $stmt = $pdo->prepare("UPDATE users SET password = ? WHERE id = ?");
                        $stmt->execute([
                            $hashedPassword,
                            $_SESSION['user_id']
                        ]);
                    }
                }

                if ($error === '') {
                    $message = 'Profile updated successfully.';
                }
            }
        }
    }
}

// Load current user data safely
$stmt = $pdo->prepare("SELECT id, name, email, role, bio FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$currentUser = $stmt->fetch(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OWASP Secure Demo</title>
</head>
<body>
    <h1>OWASP Secure PHP Demo</h1>

    <p><strong>Logged in as:</strong> <?php echo htmlspecialchars($currentUser['name']); ?> (<?php echo htmlspecialchars($currentUser['role']); ?>)</p>

    <?php if ($message): ?>
        <p style="color: green;"><?php echo htmlspecialchars($message); ?></p>
    <?php endif; ?>

    <?php if ($error): ?>
        <p style="color: red;"><?php echo htmlspecialchars($error); ?></p>
    <?php endif; ?>

    <h2>Update Profile</h2>

    <form method="POST">
        <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">

        <label>Name:</label><br>
        <input type="text" name="name" value="<?php echo htmlspecialchars($currentUser['name']); ?>"><br><br>

        <label>Bio:</label><br>
        <textarea name="bio" rows="4" cols="40"><?php echo htmlspecialchars($currentUser['bio']); ?></textarea><br><br>

        <label>New Password (optional):</label><br>
        <input type="password" name="password"><br><br>

        <button type="submit">Save</button>
    </form>

    <h2>Why this is secure</h2>
    <ul>
        <li>Prepared statements reduce SQL injection risk.</li>
        <li>htmlspecialchars() helps protect HTML output from XSS.</li>
        <li>CSRF token blocks forged form submissions.</li>
        <li>Session check is a basic authorization control.</li>
        <li>password_hash() stores passwords safely.</li>
    </ul>
</body>
</html>
 

How this example connects to OWASP

This demo shows the most practical OWASP habits in one place. The database queries use prepared statements, which is the core defense against SQL injection. The page escapes output with htmlspecialchars(), which is the right mindset for XSS prevention. The form includes a CSRF token, because OWASP explains that authenticated requests can be tricked by a malicious site without that protection. The code also checks the logged-in user before allowing updates, which is a simple form of authorization control. Finally, passwords are hashed instead of stored in plain text, which matches OWASP password storage guidance.


Good habits I follow in real PHP projects

When I work on real applications, I keep these habits in mind:

I never trust user input. I validate it first, then process it. I use prepared statements for database queries instead of concatenating SQL strings. I escape output before showing it in HTML. I protect every state-changing request with a CSRF token. I check permission before sensitive actions. I hash passwords properly. These habits line up directly with OWASP’s cheat sheet guidance.


Final Thoughts

OWASP is not only for security engineers. It is for every PHP developer who wants to build safer software. The OWASP Top 10 gives you the big risks to watch, and the Cheat Sheet Series gives you practical ways to handle them. Together, they form a very useful guide for writing professional web applications.