Debug School

rakesh kumar
rakesh kumar

Posted on

Laravel Input Validation Security Guide: Why, When and How to Limit Files, Strings and Arrays

Protect your application from oversized inputs, malformed data and unnecessary processing with practical PHP and Laravel examples.

What does input validation mean?

Input validation checks whether submitted data meets your application's requirements before the application saves it or performs expensive work.

For a document submission, ask:

  • Is the title text, and is it short enough?
  • Is the description within the permitted length?
  • Are there too many tags?
  • Is every tag valid?
  • Is the upload an allowed file type and size?

These are application rules. Put Laravel rules in a Form Request under app/Http/Requests/ or use $request->validate() in a controller. They are separate from PHP and web-server configuration.

Why is validation important for security?

Security role Example Benefit
Bound accepted input Title limited to 200 characters Limits downstream text handling and storage
Bound repeated work At most 20 tags Limits loops and related database operations
Enforce expected types Title must be a string Rejects an array submitted in place of text
Restrict uploads PDF only, up to 10 MiB Reduces accepted formats and file sizes
Control processing cost Image dimensions limited Reduces exposure to very large decoded images
Restrict nested structure Traveller object allows only name and age Rejects unexpected keys

Validation helps protect availability and data quality. It does not replace authentication, authorization, escaped output, parameterized queries, malware scanning or PHP security updates.

When should validation run?

Validate every untrusted entry point: browser forms, mobile APIs, administrative forms, imports and external integrations. Apply suitable equivalent rules to jobs that receive untrusted data.

Run validation before database writes, email generation, image resizing, document conversion or other expensive processing. Browser attributes such as maxlength and accept improve usability but can be bypassed.

Laravel validation happens after the request has reached PHP. Apply request-body limits at the web server as well, because validation cannot undo memory already used while receiving or parsing a request.

How does Laravel rule syntax work?

'title' => ['required', 'string', 'max:200'],
Enter fullscreen mode Exit fullscreen mode

Meaning: the title is mandatory, must be text and may contain at most 200 characters.

What does max measure?

Input type Syntax Meaning
String string,max:200 At most 200 characters
Array array,max:20 At most 20 elements
File file,max:10240 At most 10,240 Laravel file-size kilobytes: 10 MiB
Integer integer,max:20 Numeric value no greater than 20

Specify the intended type alongside the size rule. String characters and storage bytes are different measurements; align validation with database and encoding requirements.

What does each rule do?

Rule Meaning Purpose
required Field must be present and non-empty Reject missing essential input
nullable Field may contain null Support optional values
string Value must be text Reject unexpected structures
array Value must be an array Enforce collection structure
max:200 Maximum depends on field type Bound accepted size
tags.* Validate every tag Check each element, not only its parent
file Successfully uploaded file Reject non-upload values
mimes:pdf Infer permitted type from content Restrict accepted file formats
dimensions:max_width=4000,max_height=4000 Bound image width and height Restrict pixel dimensions
bail Stop later rules for a field after failure Avoid unnecessary checks on that field

nullable does not disable other rules for non-null values. mimes:pdf does not require a .pdf filename and does not prove a PDF is harmless.

How do I validate a document form in Laravel?

Generate a request class:

php artisan make:request StoreDocumentRequest
Enter fullscreen mode Exit fullscreen mode

File: app/Http/Requests/StoreDocumentRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreDocumentRequest extends FormRequest
{
    public function authorize(): bool
    {
        // Use a policy when access depends on a resource or role.
        return $this->user() !== null;
    }

    public function rules(): array
    {
        return [
            'title' => ['bail', 'required', 'string', 'max:200'],
            'description' => ['bail', 'nullable', 'string', 'max:20000'],
            'tags' => ['bail', 'nullable', 'array', 'max:20'],
            'tags.*' => ['bail', 'required', 'string', 'max:50'],
            'document' => [
                'bail', 'required', 'file', 'mimes:pdf', 'max:10240',
            ],
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

Controller example:

use App\Http\Requests\StoreDocumentRequest;

public function store(StoreDocumentRequest $request)
{
    $data = $request->validated();

    // Perform authorized storage and explicit database writes here.
    // Use a configured private disk for sensitive documents.

    return response()->json([
        'message' => 'Document accepted.',
        'title' => $data['title'],
    ], 201);
}
Enter fullscreen mode Exit fullscreen mode

Then what happens?

Submission Expected result
Title containing 200 characters Passes the title length rule
Title containing 201 characters Fails
Title supplied as an array Fails
Null description Passes
21 tags Fails
One tag containing 51 characters Fails
Valid 8 MiB PDF Passes these file checks
11 MiB PDF Fails if it reaches application validation
PHP script renamed invoice.pdf Renaming alone cannot satisfy content-based type checking

JSON requests expecting JSON normally receive HTTP 422 for validation failures. Browser forms normally redirect back with errors. Earlier server or PHP limits can reject an upload before these rules run.

Why must I limit both arrays and their elements?

'tags' => ['array', 'max:20'],
'tags.*' => ['required', 'string', 'max:50'],
Enter fullscreen mode Exit fullscreen mode

The first line bounds the count. The second bounds every value. Twenty enormous strings are still expensive, and thousands of short strings still create excessive repeated work.

For nested structures, validate each level:

$validated = $request->validate([
    'travellers' => ['bail', 'required', 'array', 'min:1', 'max:10'],
    'travellers.*' => ['bail', 'required', 'array:name,age'],
    'travellers.*.name' => ['bail', 'required', 'string', 'max:100'],
    'travellers.*.age' => ['bail', 'required', 'integer', 'min:0', 'max:120'],
]);
Enter fullscreen mode Exit fullscreen mode

The array:name,age rule rejects additional keys such as is_admin. Use explicit validated fields in subsequent writes.

How do I validate vehicle photographs?

$validated = $request->validate([
    'photos' => ['bail', 'required', 'array', 'min:1', 'max:5'],
    'photos.*' => [
        'bail',
        'required',
        'file',
        'image',
        'mimes:jpg,jpeg,png,webp',
        'max:5120',
        'dimensions:max_width=4000,max_height=4000',
    ],
]);
Enter fullscreen mode Exit fullscreen mode

This example accepts up to five photos, each at most 5 MiB and at most 4,000 pixels in either dimension.

Why is file size alone insufficient?

A compressed image may be small on disk but large after decoding. A 12,000 × 12,000 image contains 144 million pixels. At an illustrative four bytes per pixel, a single buffer requires about 549 MiB. Resizing may allocate additional buffers.

Dimension checks help constrain accepted images, but parsing itself still needs resources. Keep image libraries patched and bound processing memory, time and concurrency. For PDFs, add processor-specific page and time limits; for archives, control entry count and decompressed size.

How do I perform similar checks in plain PHP?

Validate strings and arrays

This example requires the mbstring extension:

<?php

function validFields(array $input): bool
{
    $title = $input['title'] ?? null;
    $description = $input['description'] ?? null;
    $tags = $input['tags'] ?? null;

    if (!is_string($title)) {
        return false;
    }
    if (trim($title) === '' || mb_strlen($title, 'UTF-8') > 200) {
        return false;
    }

    if ($description !== null) {
        if (!is_string($description)) {
            return false;
        }
        if (mb_strlen($description, 'UTF-8') > 20000) {
            return false;
        }
    }

    if ($tags !== null) {
        if (!is_array($tags) || count($tags) > 20) {
            return false;
        }
        foreach ($tags as $tag) {
            if (!is_string($tag)) {
                return false;
            }
            if (trim($tag) === '' || mb_strlen($tag, 'UTF-8') > 50) {
                return false;
            }
        }
    }

    return true;
}
Enter fullscreen mode Exit fullscreen mode

Validate a PDF upload

Use a multipart form with enctype="multipart/form-data".

<?php

function rejectUpload(string $message): never
{
    http_response_code(422);
    exit($message);
}

$file = $_FILES['document'] ?? null;

if (!is_array($file)) {
    rejectUpload('Document is required.');
}

if (
    !isset($file['error'], $file['tmp_name']) ||
    !is_int($file['error']) ||
    !is_string($file['tmp_name'])
) {
    rejectUpload('Invalid upload structure.');
}

if ($file['error'] !== UPLOAD_ERR_OK) {
    rejectUpload('Upload failed.');
}

$tmp = $file['tmp_name'];

if (!is_uploaded_file($tmp)) {
    rejectUpload('Invalid uploaded file.');
}

$size = filesize($tmp);

if ($size === false || $size === 0 || $size > 10 * 1024 * 1024) {
    rejectUpload('Document must be non-empty and no larger than 10 MiB.');
}

$mime = (new finfo(FILEINFO_MIME_TYPE))->file($tmp);

if ($mime !== 'application/pdf') {
    rejectUpload('Only PDF documents are accepted.');
}

// Continue with authorized private storage and required scanning.
Enter fullscreen mode Exit fullscreen mode

This example uses PHP 8.1+ syntax and the Fileinfo extension. Never trust the browser-supplied $_FILES['document']['type'] or original filename as proof of content.

Which production rules are appropriate, and which are risky?

These are example budgets, not universal security defaults. Match them to legitimate usage, database schema and measured processing costs.

Input or operation Appropriate example Risky approach Security concern
Title required, string, max:200 required only No type or length bound
Description nullable, string, max:20000 nullable only Non-null data unconstrained
Tags array, max:20 array only Unbounded item count
Tag values tags.* with string and max:50 Parent validation only Large or malformed elements
PDF file, mimes:pdf, max:10240 Extension check only Misleading filename
Photos Count, file size and dimensions image rule only Processing cost insufficiently bounded
Nested objects Allowed keys and child rules Arbitrary keys accepted Unexpected fields reach later code
Client validation Browser and server checks Browser checks only Direct requests bypass controls
Database writes Explicit validated fields Blind use of request->all() Unvalidated data reaches persistence
File processing Bounded workers and quotas Unlimited conversion requests CPU, memory or queue exhaustion

How should Laravel, PHP and Apache limits work together?

For one PDF of up to 10 MiB:

Layer Example setting Purpose
Laravel max:10240 Application file limit
PHP upload_max_filesize = 10M Per-file upload ceiling
PHP post_max_size = 12M Total POST budget with overhead
Apache LimitRequestBody 12582912 Total request-body ceiling of 12 MiB

Apply settings to the configuration actually serving the website. A form permitting five 5 MiB photos needs a larger total request budget than this single-document example. Include all files, fields and multipart overhead.

Then how do I verify the protection?

  1. Submit valid data and confirm success.
  2. Test each string at its limit and one character beyond it.
  3. Submit arrays at the maximum count and one element beyond it.
  4. Submit an array where a string is expected.
  5. Submit oversized individual elements and unexpected nested keys.
  6. Test allowed, renamed and oversized files.
  7. Test oversized dimensions before enabling image transformations.
  8. Confirm oversized bodies are rejected by the web server, normally with HTTP 413.
  9. Confirm application validation failures return useful errors without exposing internals.
  10. Measure memory and processing time with realistic concurrent traffic.

Sources and further reading

Colorful architecture: layered input validation and controlled processing

The web server bounds incoming bytes. Laravel checks application requirements. Accepted data proceeds to authorized storage or controlled processing; rejected input stops at the relevant layer.

flowchart TD
    A["Browser or mobile API"] --> B["Web server: body and connection limits"]
    B --> C["PHP: upload and POST limits"]
    C --> D["Authentication and authorization"]
    D --> E{"Laravel input validation"}
    E -->|"Text"| F["String type and length"]
    E -->|"Collections"| G["Array count, keys and elements"]
    E -->|"Uploads"| H["File type, size and dimensions"]
    F --> I{"All applicable checks pass?"}
    G --> I
    H --> I
    I -->|"No"| J["Reject with validation errors"]
    I -->|"Yes"| K["Explicit validated fields"]
    K --> L["Authorized database writes"]
    K --> M["Private file storage"]
    M --> N["Bounded scanning or conversion jobs"]
    N --> O["Authorized download or processed output"]

    classDef client fill:#DBEAFE,stroke:#2563EB,color:#172554;
    classDef boundary fill:#FEF3C7,stroke:#D97706,color:#78350F;
    classDef checks fill:#EDE9FE,stroke:#7C3AED,color:#3B0764;
    classDef accepted fill:#DCFCE7,stroke:#16A34A,color:#14532D;
    classDef rejected fill:#FEE2E2,stroke:#DC2626,color:#7F1D1D;
    classDef processing fill:#CFFAFE,stroke:#0891B2,color:#164E63;

    class A client;
    class B,C,D boundary;
    class E,F,G,H,I checks;
    class K,L,M accepted;
    class J rejected;
    class N,O processing;

Diagram rendering requires a Markdown platform with Mermaid support.

Top comments (0)