DEV Community

Fongoh Martin T.
Fongoh Martin T.

Posted on

Laravel 11 Security Audit Guide (Part 3 of 3)

Laravel 11 Security Audit Guide (Part 3 of 3)

Welcome to the final part of our Laravel 11 security audit series. If you’ve made it this far, you already understand the importance of hardening your app against the most common vulnerabilities.
In case you have not read the previous, you can read part 1 here and part 2 here
In this part, we’re going to cover four final security risks that many Laravel developers overlook: verbose error disclosure, missing security headers, flawed access control, and business logic abuse.

9. Verbose Error Disclosure

The Problem

Detailed error messages can be a goldmine for attackers. When exceptions throw full stack traces, database details, file paths, or even snippets of your .env file, you are exposing your internal workings. Laravel’s default error handler is very verbose in development, which is good for debugging, but dangerous in production if not configured properly.

Attackers use this information to map out your environment and identify weak points like specific packages, database credentials, or server paths.

How to Test

  • Force errors by visiting undefined routes or passing invalid data to endpoints.
  • Look for details like table names, file paths, or debug traces.

For example, visiting:

https://yourapp.com/api/user?id=abc123
Enter fullscreen mode Exit fullscreen mode

Might trigger a TypeError if the system expects an integer. If the page shows you a full stack trace, it’s revealing too much.

The Fix

Set the APP_DEBUG=false and APP_ENV=production in your .env file. This will ensure Laravel shows a simple 500 error page instead of a detailed exception.

APP_ENV=production
APP_DEBUG=false
Enter fullscreen mode Exit fullscreen mode

Also, consider customizing the render() method in app/Exceptions/Handler.php to log details while showing users a friendly error page.

Additional Advice

  • Use external logging tools like Sentry or Bugsnag.
  • Do not return exception messages in API responses.
  • Use Laravel's report() method for sensitive logging instead of exposing details in the UI.

10. Missing Security Headers

The Problem

HTTP security headers provide critical instructions to browsers about how your site should behave. Without them, your site is vulnerable to clickjacking, MIME-type sniffing, and client-side attacks like XSS.

Headers like X-Frame-Options, Content-Security-Policy, Strict-Transport-Security, and X-Content-Type-Options are commonly missing from Laravel apps unless explicitly added.

How to Test

  • Use your browser’s DevTools to inspect response headers.
  • You can also use services like securityheaders.com to quickly audit your site.

If you don’t see headers like these, it’s time to fix that:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'
Enter fullscreen mode Exit fullscreen mode

The Fix

Use middleware to inject headers into every response. Create a custom middleware:

public function handle($request, Closure $next)
{
    $response = $next($request);

    $response->headers->set('X-Frame-Options', 'DENY');
    $response->headers->set('X-Content-Type-Options', 'nosniff');
    $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
    $response->headers->set('Content-Security-Policy', "default-src 'self'");

    return $response;
}
Enter fullscreen mode Exit fullscreen mode

Register the middleware globally or per route group.

Additional Advice

  • Use a CSP library like spatie/laravel-csp for managing policies more dynamically.
  • Always serve your app over HTTPS to enforce HSTS.
  • Set Referrer-Policy and Permissions-Policy headers for additional privacy and control.

11. Flawed Access Control

The Problem

Sometimes developers forget to enforce role-based permissions, assuming that if a user is logged in, they can perform any action. This mistake can let regular users access admin routes or perform unauthorized actions like deleting other users' data.

This is not about authentication, but about authorization. Your app might know who the user is, but still fail to check if they’re allowed to do what they’re trying to do.

How to Test

  • Login as a non-admin user.
  • Try accessing URLs like /admin, /settings, or /users/delete/5.
  • Use Postman to call endpoints intended for other roles.

If you can perform actions or view pages meant for admins, the app has broken access control.

The Fix

Use Laravel's Gate and Policy systems. In controllers, always validate permissions like so:

$this->authorize('delete', $user);
Enter fullscreen mode Exit fullscreen mode

Or use middleware for role checks:

Route::middleware(['auth', 'can:isAdmin'])->group(function () {
    Route::get('/admin', [AdminController::class, 'index']);
});
Enter fullscreen mode Exit fullscreen mode

Create a custom middleware like this:

public function handle($request, Closure $next)
{
    if (auth()->user()->role !== 'admin') {
        abort(403);
    }
    return $next($request);
}
Enter fullscreen mode Exit fullscreen mode

Additional Advice

  • Never hide routes or rely on frontend logic alone.
  • Test each route from every user role.
  • Store roles and permissions securely and check them consistently.

12. Business Logic Abuse

The Problem

Business logic flaws are subtle but dangerous. They occur when users exploit the normal flow of your application to do something unintended. For example, modifying an item’s price during checkout, placing multiple discount codes, or bypassing a payment step.

The issue isn’t about bypassing security walls, it’s about misusing allowed features in a way developers didn’t expect.

How to Test

  • Tamper with request payloads in Postman or your browser's DevTools.
  • Modify price fields, quantities, or payment flags.
  • Try skipping steps in a process, like completing checkout without paying.

If any of these succeed without server-side validation, you’re vulnerable.

The Fix

  • Always validate critical data server-side, not just in JavaScript.
  • Do not trust values like price, total, or is_paid from the frontend.
  • Recalculate values on the server before processing or storing them.

Example:

$order->total = $order->items->sum(function ($item) {
    return $item->quantity * $item->product->price;
});
Enter fullscreen mode Exit fullscreen mode

Additional Advice

  • Set up alerts for strange user behavior like repeated refunds or excessive discounts.
  • Log important business actions like order confirmation, payment, and status changes.
  • Perform peer reviews for flows that involve money, discounts, or user roles.

This wraps up our Laravel 11 security audit series. From brute force prevention to business logic protection, securing your Laravel app is an ongoing job, not a one-time task. Regularly audit, test, and review your code as your product grows.

If you’ve found this series useful, consider sharing it with your team or dev circle. Let’s keep our ecosystems safe together.

Top comments (0)