DEV Community

hbgl
hbgl

Posted on

1 2

Backend shorts - Validate all your inputs

Whenever you are dealing with untrusted data, such as input from a HTTP request, you must validate it thoroughly in the backend of your web application.

function store(Request $request)
{
    $validated = $this->validate($request, [
        'name' => 'required|string|max:255',
        'unit_price' => 'required|int|min:0|max:10000',
        'currency' => 'required|in:USD,EUR',
        'stock' => 'required|int|min:0|max: 1000000',
    ]);

    $product = new Product($validated);
    $product->save();

    return redirect()->route('products.show', ['product' => $product->id]);
}
Enter fullscreen mode Exit fullscreen mode

One of the easiest and safest ways to read request data in Laravel is the $this->validate function inside a controller. It returns an array that contains the valid data (and only the valid data), or it throws a ValidationException if the data is invalid.

You should generally avoid reading data directly from the request object with $request->get('field') or similar methods, because it is very easy to forget to add the necessary validation. Reading data directly from the request object is a code smell.

Takeaways

  • When it comes to user input, be paranoid.
  • Always validate in the backend. Frontend validation offers no protection (although it is good for the UX).
  • Avoid reading unvalidated data directly from the request.

An elephant stretching for some leaves on a tree
Photo by Filip Olsok from Pexels

Bonus

Sometimes untrusted input data flies under the radar because it is not immediately accessible. A common example for this would be a CSV import where you first need to parse the CSV content in order to get to the fields. Nevertheless, it is vital to validate the parsed data.

public function importStore(Request $request)
{
    // Validate the file metadata.
    $file = $this->validate($request, [
        'file' => 'required|file|mimes:txt,csv|max:5120',
    ])['file'];

    // Load CSV.
    $csv = Reader::createFromPath($file->path());
    $csv->setHeaderOffset(0);
    $header = $csv->getHeader();
    $input = iterator_to_array($csv->getRecords(), false);

    // Set a limit for the maximum number of products per import.
    if (count($input) > 1000) {
        throw ValidationException::withMessages([
            'file' => 'The import is limited to 1000 rows.',
        ]);
    }

    // Do a quick check to see if the header is correct. Although the
    // validation logic further below would also find the error, it
    // would produce multiple error messages for each line in the file.
    if ($header !== ['name', 'unit_price', 'currency', 'stock']) {
        throw ValidationException::withMessages([
            'file' => 'The CSV file does not have the right header.',
        ]);
    }

    // Validate CSV file content.
    $validated = Validator::make($input, [
        '*' => 'required|array',
        '*.name' => 'required|string|max:255',
        '*.unit_price' => 'required|int|min:0|max:10000',
        '*.currency' => 'required|in:USD,EUR',
        '*.stock' => 'required|int|min:0|max: 1000000',
    ])->validate();

    $instant = now();
    foreach ($validated as &$entry) {
        $entry['created_at'] = $instant;
        $entry['updated_at'] = $instant;
    }

    Product::insert($validated);

    return redirect()->route('products.index');
}
Enter fullscreen mode Exit fullscreen mode

AWS Q Developer image

Your AI Code Assistant

Implement features, document your code, or refactor your projects.
Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay