3 Laravel Security Features You Need to Know (With Examples)
Laravel has features that make your web application more resilient to attacks. Today I will show you how to protect your web application using Laravel.
CSRF attack protection
Cross-site request forgery (CSRF) is a type of attack that causes actions to be performed that the user didn’t intend to perform.
How does a CSRF attack work?
Let’s take the example of the functionality of changing the user’s еmail. We will see how the CSRF attack works using the example of the endpoint GET method because POST requests in Laravel are heavily protected. Even with CSRF protection disabled for the endpoint, the current user isn’t returned during POST requests if the request is sent from another domain. Setting up routes:
use App\Http\Controllers\UserController;
Route::get('/change_email', [UserController::class, 'changeEmail']);Simple logic for changing the email in the controller:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
public function changeEmail(Request $request): void
{
$user = Auth::user();
$user->email = $request->input('email');
$user->save();
}To demonstrate how a CSRF attack works, we need to create an HTML page like this.
<form action="https://your-site.com/change_email"
method="GET"
id="csrf-attack-form">
<input name="email" type="hidden" value="dangerous@email.com">
</form>
<script type="text/javascript">
document.getElementById('csrf-attack-form').submit();
</script>An attacker can create a page with such content and send it to the user. When the user opens the page, JavaScript will automatically submit the form and the form will send a request to change the email. If the user is authorized, the email will change. By changing the email to their own, the attacker can access the user’s account. This example is too simplified to explain CSRF attacks.
Laravel doesn’t support CSRF protection for GET requests. So when you need to change the state of something, always use POST, PUT, PATCH, or DELETE requests.
To protect against CSRF attacks, Laravel generates a token that needs to be passed to the POST endpoint. This protects against sending requests from external sources.
For your Blade form to work correctly, you need to submit the CSRF token using the @csrf Blade directive. @csrf adds a hidden field to the form with the token value.
<form action="/change_email" method="POST" >
@csrf
<input name="email" type="text">
<button>Change</button>
</form>You can also disable CSRF protection for specific endpoints in bootstrap/app.php. I recommend doing this only in cases where it is very necessary. For example, when you have an external service that connects to an endpoint.
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'change_email',
]);
})XSS attack protection
Cross-site scripting (XSS) is a type of attack that injects malicious script that can run in the user’s browser.
How does an XSS attack work?
Imagine you have a form for adding posts to the site by users. You publish the added posts on your site using Blade:
<h1>{!! $title !!}</h1>
<p>{!! $title !!}</p>Using the form, an attacker can add a script like this:
<script>alert(document.cookies)</script>This script is saved as text in the database. If you output the data in an unprotected way, the script will run in the browser of the user who reads this post. Thus, the attacker can steal cookies and other user information.
If you output information in Blade using {!! !!} brackets then malicious scripts will be executed in the user’s browser. Using {!! !!} brackets to output data added by other users are unsafe. Always use {{ }} brackets for these purposes.
For security, you can filter the data you receive from the user before storing it in the database.
Protection against SQL injections
SQL injection is the introduction of malicious SQL code into a database.
How does a SQL injection work?
Imagine you need to create a feature where a user enters a code and gets a coupon. If you do it this way, your code will be vulnerable to SQL injections:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
public function getCoupon(Request $request)
{
$code = $request->input('code');
$coupons = DB::select('SELECT name FROM coupons WHERE code=' . $code);
return view('coupons', compact('coupons'));
}The user can send to the server such a value “123 OR 1=1”. The second condition will always be true and the user can receive coupons that don’t belong to him.
This query will return all coupons, even those that don’t belong to the user:
SELECT name FROM coupons WHERE code=123 OR 1=1Therefore, always use named bindings for data you receive from the user.
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
public function getCoupon(Request $request)
{
$code = $request->input('code');
$coupons = DB::select('SELECT * FROM messages WHERE code = :code', [
'code' => $code
]);
return view('coupons', compact('coupons'));
}It will be more efficient to use Eloquent ORM to solve this problem.
You can also follow me on LinkedIn and X account where I publish PHP and Laravel Tips.
