How to secure input in php - with complete example

How to secure input in php - with complete example - Image

User input is one of the biggest security concerns in any PHP application. A value submitted through a form, URL, cookie, API request, or other client-controlled source should never be treated as trustworthy.

A common mistake is to assume that removing a few characters from user input makes an application secure. In reality, secure PHP development requires several techniques working together: validate data according to its expected type, sanitize only when appropriate, escape data for its output context, use prepared SQL statements, and protect sensitive actions against attacks such as CSRF.

The original article focuses mainly on a custom sanitization function. A more reliable modern approach is to avoid trying to create one universal “sanitize everything” function. PHP's own documentation distinguishes validation from sanitization, and the correct technique depends on what you intend to do with the data.

Why Should You Secure User Input in PHP?

Imagine a PHP form that accepts a username:

$username = $_POST['username'];

The value could be perfectly normal:

Rahul

But the server has no guarantee that every visitor will send normal text. Someone could submit HTML, JavaScript, unexpected characters, extremely long input, malformed data, or content designed to manipulate a database query.

Depending on where that value is used, the result could include:

  • Cross-site scripting (XSS)
  • SQL injection
  • Invalid application data
  • Authentication problems
  • Unexpected application behavior
  • Data corruption
  • Abuse of business logic

The key principle is simple:

Never trust data simply because it came from your own website.

Client-side validation can improve the user experience, but it cannot be your security boundary. Attackers can send requests directly to your server without using your HTML form.

Validation vs Sanitization in PHP

One of the most important concepts in secure PHP development is understanding the difference between validation and sanitization.

What Is Input Validation?

Validation asks:

“Does this value meet the rules I expect?”

For example, if a field should contain an integer:

$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);

if ($age === false || $age === null) {
    echo "Invalid age.";
}

If the field should contain an email address:

$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);

if ($email === false || $email === null) {
    echo "Please enter a valid email address.";
}

PHP provides validation filters such as FILTER_VALIDATE_EMAIL and FILTER_VALIDATE_INT. Validation checks whether data meets a requirement rather than simply changing the submitted value.

What Is Sanitization?

Sanitization attempts to modify input by removing or transforming characters.

For example, PHP provides sanitization filters through the Filter extension. However, sanitization should not be treated as a universal security solution. Different output contexts and data types require different handling.

A better question than “How do I sanitize everything?” is:

“What should this value be, and where will I use it?”

That question leads to safer code.

Validate Input According to Its Expected Type

The safest input is usually input that has been restricted to a clearly defined format.

Suppose a product ID should be an integer:

$productId = filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT
);

if ($productId === false || $productId === null) {
    http_response_code(400);
    exit('Invalid product ID.');
}

If a field should contain a date, use an appropriate date-validation strategy. If it should contain a limited set of values, compare it against an allowlist.

For example:

$allowedSorts = ['price', 'name', 'rating'];

$sort = $_GET['sort'] ?? 'name';

if (!in_array($sort, $allowedSorts, true)) {
    $sort = 'name';
}

This is particularly important when user input influences something that cannot be represented by a normal SQL parameter, such as a column name or sort direction. PHP's security documentation recommends filtering dynamic SQL components against known allowed values.

Use Allowlists for Fixed Choices

If a value is supposed to be one of three options, don't accept arbitrary text and attempt to remove dangerous characters.

Use an allowlist:

$allowedRoles = ['user', 'editor', 'admin'];

$role = $_POST['role'] ?? 'user';

if (!in_array($role, $allowedRoles, true)) {
    $role = 'user';
}

This is much easier to reason about than trying to predict every possible malicious string.

Never Put Raw User Input Directly Into SQL

SQL injection is one of the most important risks to understand when working with PHP and databases.

This is unsafe:

$username = $_POST['username'];

$sql = "SELECT * FROM users WHERE username = '$username'";

The problem is that the user's value becomes part of the SQL command itself.

Instead, use a prepared statement with parameters.

Use PDO Prepared Statements

$username = $_POST['username'] ?? '';

$pdo = new PDO(
    'mysql:host=localhost;dbname=myapp;charset=utf8mb4',
    'app_user',
    'password'
);

$stmt = $pdo->prepare(
    'SELECT id, username FROM users WHERE username = :username'
);

$stmt->execute([
    'username' => $username
]);

$user = $stmt->fetch(PDO::FETCH_ASSOC);

Here, the SQL structure and the user-supplied value are handled separately.

PHP's documentation specifically recommends binding data through prepared statements to help prevent SQL injection.

Do Not Build SQL With String Concatenation

Avoid patterns such as:

$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];

Even if you cast some values to integers, prepared statements should remain the standard approach for SQL data values.

Prepared statements are not just about escaping strings. They provide a separation between SQL instructions and the values supplied to those instructions.

Escape Output to Prevent XSS

Validation and SQL protection do not automatically protect HTML output.

Suppose a user submits:

<script>alert('Hello')</script>

If you later place that value directly into an HTML page:

echo $username;

you may create a cross-site scripting vulnerability.

For HTML text, use htmlspecialchars():

echo htmlspecialchars(
    $username,
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);

This converts characters with special meaning in HTML into safe representations.

Escape at the Point of Output

A useful security habit is:

Validate when receiving data. Escape when displaying data.

Do not assume that escaping the value once makes it safe for every future context.

HTML, JavaScript, URLs, CSS, SQL, and shell commands all have different rules. A value that is safe for one context may not automatically be safe for another.

For example, data inserted into an HTML attribute requires appropriate HTML escaping:

<input
    type="text"
    name="username"
    value="<?= htmlspecialchars($username, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"
>

Handle Form Input Carefully

A typical PHP form should not assume that a field exists.

Instead of:

$name = $_POST['name'];

use:

$name = $_POST['name'] ?? '';

Then validate it:

$name = trim($_POST['name'] ?? '');

if ($name === '') {
    $errors[] = 'Name is required.';
}

if (mb_strlen($name) > 100) {
    $errors[] = 'Name is too long.';
}

This approach handles missing fields and gives you an opportunity to enforce application-specific rules.

Validate Length as Well as Format

A value can have the correct general format but still be unreasonably large.

For example:

$message = trim($_POST['message'] ?? '');

if (mb_strlen($message) > 5000) {
    $errors[] = 'Message is too long.';
}

Reasonable length limits can help protect application resources and prevent unexpected data from entering the system.

Be Careful With filter_input()

PHP's filter_input() function can retrieve and optionally filter external variables such as GET and POST values.

For example:

$email = filter_input(
    INPUT_POST,
    'email',
    FILTER_VALIDATE_EMAIL
);

One important detail is that FILTER_DEFAULT does not mean “secure the input.” PHP documents FILTER_DEFAULT as an alias for FILTER_UNSAFE_RAW, meaning no filtering occurs by default.

Therefore, this:

filter_input(INPUT_POST, 'name');

should not be interpreted as a security mechanism.

Specify the validation or filtering behavior you actually need.

Protect Forms Against CSRF

Securing the submitted value itself is only part of the problem.

Cross-Site Request Forgery (CSRF) occurs when an attacker attempts to cause a user's browser to perform an action on a website where the user is already authenticated.

For state-changing forms, a common defense is a CSRF token.

Generate a token when creating the session/form:

session_start();

if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

Place it in the form:

<input
    type="hidden"
    name="csrf_token"
    value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8') ?>"
>

Then verify it when processing the request:

session_start();

$token = $_POST['csrf_token'] ?? '';

if (
    !hash_equals(
        $_SESSION['csrf_token'] ?? '',
        $token
    )
) {
    http_response_code(403);
    exit('Invalid request.');
}

The token helps the application distinguish legitimate requests originating from its own interface from forged requests.

Don't Trust Hidden Fields or Cookies

A hidden form field can look trustworthy:

<input type="hidden" name="role" value="user">

But a visitor can modify it before sending the request.

The same applies to:

  • Cookies
  • URL parameters
  • POST values
  • HTTP headers
  • Hidden inputs
  • Client-side JavaScript values

Never assume that a value is safe because the browser normally generates it.

PHP's SQL injection guidance explicitly emphasizes that input from the client should not be trusted, including values coming from hidden fields and cookies.

Avoid Creating One “Universal Sanitization” Function

A common approach is to create a function such as:

function sanitize_input($value) {
    // Remove unwanted characters
}

and then pass every input through it.

The problem is that different data has different requirements.

An email address, username, HTML fragment, search query, database value, filename, and URL should not necessarily be treated in the same way.

For example:

$email = filter_var($email, FILTER_VALIDATE_EMAIL);

may make sense for an email address.

For HTML output:

echo htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

is appropriate for an HTML context.

For SQL:

$stmt->execute(['name' => $name]);

is preferable to manually escaping the value.

Security works better when each operation uses the defense designed for that operation.

A Secure PHP Form Example

Here is a simple example that combines several of these principles:

<?php

session_start();

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit('Invalid request method.');
}

$csrfToken = $_POST['csrf_token'] ?? '';

if (
    !hash_equals(
        $_SESSION['csrf_token'] ?? '',
        $csrfToken
    )
) {
    http_response_code(403);
    exit('Invalid request.');
}

$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');

$errors = [];

if ($name === '') {
    $errors[] = 'Name is required.';
} elseif (mb_strlen($name) > 100) {
    $errors[] = 'Name is too long.';
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors[] = 'A valid email address is required.';
}

if ($errors) {
    foreach ($errors as $error) {
        echo htmlspecialchars(
            $error,
            ENT_QUOTES | ENT_SUBSTITUTE,
            'UTF-8'
        );
    }

    exit;
}

// Store or process the validated data here.

The example demonstrates an important principle: there is no single function responsible for “making everything secure.”

Instead:

  1. The request method is checked.
  2. The CSRF token is verified.
  3. Input is retrieved safely.
  4. The name is validated against application rules.
  5. The email is validated using an appropriate validator.
  6. Output is escaped before being displayed.

Database operations should then use prepared statements rather than concatenating these values into SQL.

Common PHP Input Security Mistakes

Trusting Client-Side Validation

JavaScript validation is useful for usability, but it can be bypassed completely.

Always validate important data on the server.

Using htmlspecialchars() for SQL Security

htmlspecialchars() is for HTML escaping. It is not a replacement for SQL prepared statements.

Use PDO or MySQLi prepared statements for database queries.

Using SQL Escaping Instead of Prepared Statements

Manually escaping database values is more error-prone than parameterized queries. PHP recommends prepared statements with bound parameters for SQL input.

Assuming Sanitization Equals Validation

Changing an invalid value into a different value does not necessarily mean the original requirement was satisfied.

For example, if an ID must be an integer, validate that it is an integer rather than simply deleting characters until the result looks numeric.

Trusting Hidden Fields

Hidden does not mean secure. Treat hidden inputs exactly like other client-controlled data.

Escaping Too Early

If you HTML-escape a value when you first receive it, that encoded value may later be inappropriate for another context.

Keep data in the form your application expects and encode it for the specific output context.

Best Practices Checklist for Secure PHP Input

A secure PHP application should generally follow these principles:

  • Treat all client-provided data as untrusted.
  • Validate data on the server.
  • Use allowlists for fixed choices.
  • Validate values according to their expected type.
  • Apply reasonable length limits.
  • Use prepared statements for database values.
  • Never concatenate raw user input into SQL.
  • Escape data according to its output context.
  • Use CSRF protection for sensitive state-changing requests.
  • Do not rely on client-side validation.
  • Avoid one-size-fits-all sanitization functions.
  • Give database users only the permissions they actually need.
  • Keep PHP and its dependencies updated.
  • Store passwords using password hashing functions rather than reversible encryption.

FAQ

What is the safest way to secure user input in PHP?

There is no single function that makes every input safe. Validate data according to its expected format, use prepared statements for SQL, escape output for its context, and protect sensitive requests against attacks such as CSRF.

Is htmlspecialchars() enough to secure PHP input?

No. htmlspecialchars() is primarily useful for escaping data before placing it into HTML. It does not protect database queries from SQL injection and should not be treated as a general-purpose input sanitizer.

Does PHP automatically sanitize $_POST data?

No. PHP does not automatically make POST data safe for your application's intended use. PHP provides filtering and validation tools, but developers must choose the appropriate handling for each value. FILTER_DEFAULT, in particular, performs no filtering.

How can I prevent SQL injection in PHP?

Use prepared statements with bound parameters through PDO or MySQLi. Do not concatenate user input into SQL queries. PHP's documentation recommends parameterized queries as the primary defense.

Should I sanitize or validate PHP input?

Usually, start by asking what the data is supposed to contain. Validation determines whether the value meets your requirements. Sanitization modifies data and can be useful in specific situations. They are not interchangeable concepts.

Can hidden form fields be trusted?

No. Hidden fields are still controlled by the client and can be modified before submission. Validate authorization-sensitive values on the server rather than trusting what the browser sends.

Is filter_input() secure by default?

No. filter_input() can perform useful validation or sanitization when you specify a filter, but its default FILTER_DEFAULT behavior is equivalent to FILTER_UNSAFE_RAW.

How do I protect PHP forms from CSRF?

Use a server-generated CSRF token, include it in the form, and verify it on the server when processing the request. For sensitive applications, also configure session cookies and authentication mechanisms appropriately.

Conclusion

Securing input in PHP is not about finding one magic sanitization function. The stronger approach is to treat security as a series of context-specific decisions.

Validate values according to what your application expects. Use allowlists for fixed choices. Use prepared statements whenever user-controlled data reaches SQL. Escape output for the context where it will be displayed. Protect state-changing forms with CSRF defenses, and never assume that hidden fields, cookies, or client-side validation are trustworthy.

The original sanitization approach can be useful as a starting point for understanding input cleaning, but modern PHP security benefits from a more precise strategy than simply removing characters from every incoming value.

When in doubt, remember the core rule:

Validate input, parameterize SQL, and escape output for its context.

Related posts

Write a comment