Key Takeaways
- Sanitize Your Input: Always clean user input by removing or neutralizing malicious code before it’s saved to the database.
- Use a Layered Defense: Combine validation, sanitization, and output escaping to create a strong, multi-layered security system.
- Trust No One: A developer’s job is to assume all user input is a potential threat and to build in protections at every step of the process.
As a developer, the security of your web applications is a top priority, not just a bonus. A single vulnerability can compromise user trust, leak sensitive data, or even damage your reputation. One of the most common and dangerous threats you’ll face is the Cross-Site Scripting (XSS) attack.
In this article, we’ll go beyond the basics to show you how to truly secure your Laravel applications.
We’ll start by defining what sanitization is and what XSS exploits are, including the clever tricks hackers use – like event attributes and encoded code – to bypass common filters.
Then, we’ll show you how to prevent Laravel XSS exploits through a powerful two-pronged approach using validation and user input sanitization within a practical mini-project we’ll build from scratch.
Let’s get started.
Why Do We Need Input Sanitization?
User input is a primary gateway for hackers to compromise web applications. They use injection attacks like Cross-Site Scripting (XSS) and SQL Injection (SQLi) to execute unauthorized commands, steal data, or manipulate the application’s behavior.
Think of a simple comment section on a blog—without proper security, a malicious user could insert code that steals the session cookies of other visitors.
Input Sanitization is a security protocol that cleans and filters this data to prevent malicious code from being executed. It’s a crucial first line of defense.
By removing or neutralizing harmful characters and tags (like <script> or event attributes), sanitization ensures that even if an attacker attempts to inject malicious code, it’s treated as harmless text.
While it’s not a complete solution on its own, sanitization is a crucial part of a layered security strategy that every developer must understand.
What is XSS?
Cross-Site Scripting (XSS) is a type of security vulnerability that allows attackers to inject malicious code, typically JavaScript, into a web page viewed by other users. The core of an XSS attack is exploiting a website’s trust in user input.
Instead of harmless text, an attacker provides a script, which the website then renders as part of its page. This script can then execute in the victim’s browser, leading to a range of malicious activities.
A simple example of this attack can be seen on a website that displays a user’s name directly from a URL parameter.
For instance, a standard URL might look like this: https://example.com/profile?name=John
A vulnerable site would take the name parameter’s value and display it on the page. An attacker could exploit this by replacing the name with a script:
https://example.com/profile?name=<script>alert(‘You have been hacked!’);</script>
When another user visits this malicious URL, the browser executes the script, causing a pop-up alert. While this example is harmless, a real attack could be far more dangerous. Attackers could steal user session cookies, redirect users to phishing sites, or deface the website.
XSS and the Laravel Framework
Given that Laravel is a powerful framework for building web applications, understanding how XSS can affect it is crucial.
Laravel’s security features provide strong protection against these attacks, but developers must still follow best practices. Laravel’s Blade templating engine, for example, has built-in defenses against XSS, which I’ll reference later in this blog.
Possible XSS Exploitation Points
Hackers are creative, and they have many ways to inject malicious code without simply using the standard <script> tag. Understanding these methods is key to building a strong defense.
The following examples show why you can’t rely on a single, simple filter.
- Script in Attributes: Malicious code can be hidden within HTML attributes that are triggered by a user’s action or a browser event.
-
- onmouseover: This executes code when a user hovers their mouse over an element.
<b onmouseover=alert('XSS!')>hover here</b>
-
- onerror: This executes code when an element fails to load.
<img src="https://invalid.url" onerror=alert('XSS!')>
- Encoded URI Schemes: Attackers can encode characters to bypass basic string filters.HTML
<IMG SRC=jAvascript:alert('Encoded!');>
- This example uses a hexadecimal character code (A) for the letter ‘a’ in ‘javascript’, which can trick filters looking for the exact string.
- Code Encoding: Attackers can encode their entire script to make it completely unrecognizable to basic filters. For example, using Base64 encoding in a meta tag can force a page to redirect to a malicious payload.HTML
<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnWCBTUycwKTwvc2NyaXB0Pg">
The Role of Validation in Security
Laravel’s validation is your first line of defense, but it’s important to know its purpose. Validation is used to check the structure and format of the incoming data, not necessarily to remove malicious code. For our Task Manager mini-project, we used validation to ensure the input was correct:
- required: Guarantees that the field cannot be empty.
- max:255: Limits the string’s length to prevent excessively long input that could lead to other issues.
While validation helps ensure theof your data, it won’t stop XSS on its own. A string like My task <script>alert(‘XSS!’);</script> is still a valid string.
Laravel Sanitization
Sanitization is the process of cleaning and filtering user input to remove harmful or unwanted characters and code. It is a separate and crucial step after validation.
In our mini-project, we applied sanitization directly in the controller using PHP’s strip_tags() function. This function removes all HTML and PHP tags from a string, effectively neutralizing the most common XSS payloads.
public function store(Request $request)
{
// Validation
$validatedData = $request->validate([
‘title’ => ‘required|max:255’,
‘description’ => ‘required’,
]);
// Sanitization
$validatedData[‘title’] = strip_tags($validatedData[‘title’]);
$validatedData[‘description’] = strip_tags($validatedData[‘description’]);
// … save the data
}
While this method works, for larger applications, a more scalable approach is to use middleware, as it ensures sanitization is applied consistently across all relevant inputs without needing to repeat code.
Preventing XSS Laravel Exploits: A Mini-Project Tutorial
Imagine an e-commerce site where a user leaves a review. Instead of writing about the product, they insert malicious code like <script>alert(‘XSS Attack!’)</script> into the comment box.
This code then executes for every other person who views that product page, potentially stealing their session cookies or redirecting them to a fake login page.
This is a real-world example of an XSS (Cross-Site Scripting) attack.
To show you exactly how to prevent this kind of vulnerability, we’ll build a simple mini-project: a basic Task Manager.
This application will allow users to add tasks, each with a title and a description, which are then displayed on the same page.
By handling the task title and description fields with a few crucial security measures, you will learn how to sanitize user input on the server-side and properly escape output to the browser, making your application impervious to such attacks.
Prerequisites
Before we start, make sure you have the following installed on your system:
- Composer: The dependency manager for PHP.
- PHP: Version 8.0 or higher.
- XAMPP: This gives you a local Apache web server and a MySQL database, which Laravel will use.
You’ll also need a command-line interface (CMD on Windows, Terminal on macOS/Linux).
Step 1: Set Up the Laravel Project
First, we need to create a new Laravel project. Open your command line and run the following command. Replace Task-Manager with your desired project name.
composer create-project laravel/laravel Task-Manager
This command will download all the necessary files and set up a new Laravel application in a folder named Task-Manager.

Here as you can see, the project has been created:

Next, navigate into your new project directory:
cd Task-Manager

Step 2: Configure the Database
Laravel needs to know where your database is. We’ll use the MySQL database that comes with XAMPP.
- Open the XAMPP control panel and start the Apache and MySQL services.

- Open your browser and go to http://localhost/phpmyadmin.
- Click on the “Databases” tab and create a new database. Let’s name it task_manager_db.


- Go back to your code editor and open the .env file in your project’s root directory.
- Find the database section and update the settings to match your new database. If you’re using XAMPP’s default setup, the password will be empty.
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=task_manager_db DB_USERNAME=root DB_PASSWORD=

Step 3: Create the Task Model and Migration
We need a database table to store our tasks. Laravel uses a feature called migrations to manage database schemas.
Open your command line in the project directory and run the following command to create a Task model and a migration file:
php artisan make:model Task -m
This command creates two files:
- app/Models/Task.php (Our database model)
- database/migrations/xxxx_xx_xx_xxxxxx_create_tasks_table.php (Our migration file)

- Open the Task.php file. It’s located at app/Models/Task.php.
- Inside the Task class, add the following protected property:
protected $fillable = ['title', 'description'];
Here is what your complete Task.php model file should look like:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
protected $fillable = ['title', 'description'];
}
We made this change to the Task.php file because, by default, Laravel protects against a security vulnerability called Mass Assignment. Later, when we run the project, this change will prevent Laravel from throwing a MassAssignmentException error and will allow our form data to be saved correctly to the database.
Now that we’ve gotten that out of the way, let’s define the table’s structure. Open the migration file in the database/migrations folder and add the title and description columns inside the up method. The filename will look something like 2025_09_23_000000_create_tasks_table.php inside the database/migrations directory.

- Inside the up() method, find the Schema::create(‘tasks’, function (Blueprint $table) { … }) block. Add the following two lines of code before the $table->timestamps(); line:
{
$table->string('title');
$table->text('description');
Your completed up() method should look exactly like this:
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->timestamps();
});
}
}

After saving the changes, go back to your command line and run the migration command to apply these changes to your database.
php artisan migrate

This command will create the tasks table with the new id, title, description, and timestamps columns, preparing your database to store the tasks for your mini-project.
Step 4: Create the Controller and Routes
We need a controller to handle the logic for displaying and storing tasks.
php artisan make:controller TaskController
This creates a new controller file at app/Http/Controllers/TaskController.php.


Let’s add two methods to it: one to display the tasks and the form (index), and one to store a new task (store).
<?php
namespace App\Http\Controllers;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
$tasks = Task::orderBy('created_at', 'desc')->get();
return view('tasks.index', compact('tasks'));
}
public function store(Request $request)
{
// 1. Validation (for structure)
$validatedData = $request->validate([
'title' => 'required|max:255',
'description' => 'required',
]);
// 2. Sanitization (for security)
$validatedData['title'] = strip_tags($validatedData['title']);
$validatedData['description'] = strip_tags($validatedData['description']);
Task::create($validatedData);
return redirect('/');
}
}
Now, let’s create the routes to connect URLs to our controller methods. Open routes/web.php.
At the top of the file, add the use statement to import the TaskController. This tells Laravel where to find the controller class.
use App\Http\Controllers\TaskController;
Find the existing route that returns the welcome view and replace it with the two new routes you need.
Route::get(‘/’, …) will now call the index method on your TaskController.
Route::post(‘/tasks’, …) will handle the form submission and call the store method.
Your updated routes/web.php file should look like this:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TaskController; // Add this line
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
// Replace the existing route with these two lines
Route::get('/', [TaskController::class, 'index']);
Route::post('/tasks', [TaskController::class, 'store']);
After making these changes, save the file. Now, when you visit the root URL (/), Laravel will use your TaskController to display the task manager page, and when you submit the form, it will use the same controller to save the new task.
Step 5: Create the User Interface (Blade View)
Now for the front end. Create a new directory called tasks inside the resources/views folder. Inside this new directory, create a file named index.blade.php.

This view will contain the form to add tasks and the list to display them.
Notice the {{ $task->title }} and {{ $task->description }}. Laravel’s Blade templating engine automatically escapes any HTML in these double curly braces. This is our main defense against XSS on the output side.
Step 6: Test for XSS
Now for the fun part! Let’s see our security measures in action.
- Start the Laravel development server:
php artisan serve

- Open your browser and go to http://127.0.0.1:8000. You should see the Task Manager page.
- In the “Task Title” field, enter a harmless title like My first task.
- In the “Description” field, enter a malicious script, for example: <script>alert(‘XSS Attack!’);</script>.
- Click “Add Task.“
When the page reloads, you will see a new card with “My first task” as the title. For the description, you will see the script tag (<script>…) as plain text. The script did not execute!

This is because:
- The strip_tags function in our TaskController removed the script tags from the input before saving it to the database.
- Even if the tags had slipped through, Laravel’s Blade engine automatically escaped the output, rendering the script as harmless text.
And that’s it! You’ve successfully built a simple Laravel application and implemented key security measures to protect against XSS attacks.
Developers Trust Cloudways for Hosting Their Laravel Apps
Launch your Laravel apps with ease on our developer-friendly cloud platform & experience blazing-fast speeds and performance.
Conclusion
And that’s it! You now know why Validation and User Input Sanitization are important for building secure web applications.
In this blog, we looked at:
- Why user input is a major security risk and the need for a layered defense.
- What XSS exploits are and the clever ways hackers can bypass basic filters using event attributes and encoded code.
- The essential role of validation for data integrity.
- The hands-on process of sanitization using a mini-project to protect your database and users.
- How Laravel’s Blade engine provides an additional, crucial layer of output protection.
Though we created this mini-project on a local server, your actual app would live on a live server. Choosing a hosting provider that prioritizes security is just as important as the code you write.
At Cloudways, our managed Laravel hosting comes with built-in security features such as dedicated firewalls, free SSL certificates, and automated backups, giving you an extra layer of protection against threats.
If you have any questions, feel free to ask in the comments section below!
Frequently Asked Questions
Q1: What is the difference between validation and sanitization?
Validation checks if data is in the correct format (e.g., a field is required, a string is less than 255 characters). Sanitization cleans the data by removing or neutralizing malicious code (e.g., stripping script tags). You need both for robust security.
Q2: Does Laravel’s Blade automatically prevent all XSS?
No. Blade’s double curly braces ({{ … }}) automatically escape HTML, which is a powerful defense against XSS on the output side. However, it doesn’t clean the data before it’s saved to the database. If malicious code is stored and then a vulnerable application (not using Blade) displays it, the attack could still succeed. Always sanitize input before saving.
Q3: Is strip_tags() enough for sanitization?
For basic cases, strip_tags() is a good start as it removes common HTML tags. However, it may not catch more complex or encoded attacks (like event handlers in attributes). For high-security applications, it’s better to use a dedicated HTML sanitization library like HTML Purifier.
Q4: What’s a better way to sanitize all my user input in Laravel?
Instead of manually calling strip_tags() in every controller, you can create a middleware that automatically sanitizes all incoming requests. This ensures every piece of user input is cleaned consistently and without you having to remember to do it each time.
Q5: Can an attacker still execute a script even after I use strip_tags()?
Yes, in some rare cases. An attacker could use tricky, self-executing CSS or encoded URI schemes that strip_tags() might miss. This is why a layered approach is crucial, relying not just on sanitization but also on validation and, most importantly, on the output escaping provided by Blade.
Abdul Rehman
Abdul is a tech-savvy, coffee-fueled, and creatively driven marketer who loves keeping up with the latest software updates and tech gadgets. He's also a skilled technical writer who can explain complex concepts simply for a broad audience. Abdul enjoys sharing his knowledge of the Cloud industry through user manuals, documentation, and blog posts.