Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
SecurityJune 21, 20264 min read

Preventing IDOR vulnerabilities in Laravel with attribute-based access control

Prevent IDOR vulnerabilities in your Laravel apps by using attribute-based access control in Eloquent models. Secure your data with clean, automated logic.

LaravelSecurityIDOREloquentAuthorizationWeb DevelopmentWebBackend
A close-up of a padlock securing a wire fence, symbolizing protection and safety.

During an on-call rotation last year, I spent about four hours tracing a data leak where a user could view another company's invoice simply by changing an integer in the URL. We had plenty of tests, but we’d relied on manual authorization checks in our controllers, and eventually, someone missed one during a refactor.

It’s a classic story: you trust your routes, but you forget that the database doesn't know who the current user is. To fix this, I stopped putting if ($user->id !== $invoice->user_id) inside my controllers and started enforcing access constraints directly at the model layer.

Understanding the IDOR Problem

An Insecure Direct Object Reference (IDOR) happens when an application provides direct access to objects based on user-supplied input without proper validation. If your route is /invoices/{id}, your controller probably looks like this:

PHP
public function show($id) {
    $invoice = Invoice::findOrFail($id);
    return view('invoices.show', compact('invoice'));
}

The findOrFail method is a trap. It executes SELECT * FROM invoices WHERE id = ?. If the ID exists, the database returns it. It doesn't care if the logged-in user owns that invoice. You're effectively leaving the vault door open and asking the user to please not look inside.

Moving Toward Attribute-Based Access Control

Close-up of vibrant HTML code displayed on a computer screen, showcasing web development and programming.

We first tried adding global scopes to our models, but that broke our admin dashboard where moderators needed to see everything. Instead, I moved to an attribute-based approach that ties the authorization logic to the model's relationships.

If you are new to the basics of ORM usage, I recommend reviewing Eloquent basics: models, relationships, and your first queries to ensure your schema supports these checks.

The goal is to move authorization into the model's query scope so that the database cannot return an object unless the current user has permission to see it.

Implementing Secure Scopes

Instead of fetching by ID, we use a scoped query that injects the user's context. Here is how I structure this in my models:

PHP
#6A9955">// Inside your Invoice model
public function scopeForUser($query, $user)
{
    return $query->where('user_id', $user->id);
}

Now, your controller becomes much safer:

PHP
public function show($id) {
    $invoice = Invoice::forUser(auth()->user())->findOrFail($id);
    return view('invoices.show', compact('invoice'));
}

If the ID doesn't belong to the user, findOrFail throws a ModelNotFoundException, which Laravel automatically converts to a 404. The attacker doesn't even know the record exists, let alone that they shouldn't see it.

Scaling Security with Policies

While scopes handle data retrieval, Mastering Laravel Policies: A Practical Guide to Authorization Logic is still necessary for actions like updating or deleting. However, combining Policies with attribute-based access control creates a "defense in depth" strategy.

I often use a Trait to enforce this across my codebase. By creating a BelongsToUser trait, I can ensure every model that needs this protection has a standard scopeForUser method, preventing me from forgetting it in a new controller.

The Trade-off: Performance vs. Safety

I’ve found that enforcing these checks at the query level adds roughly 0.2ms to the database execution time—a cost I'm happy to pay. However, you have to be careful with complex multi-tenant applications. If you're building a SaaS, you might need to check for tenant_id instead of user_id.

If you're dealing with complex data transformations, you might be tempted to use accessors, but be careful. As I've noted in my guide on Laravel Eloquent Accessors and Mutators: A Practical Guide, those are for formatting, not for security. Never rely on an accessor to hide sensitive data; the data is already in memory by that point.

FAQ: Common Security Questions

Q: Does this replace Laravel Policies? A: No. Scopes prevent the data from being retrieved in the first place (data privacy), while Policies handle the business logic of whether a user is allowed to perform an action (authorization).

Q: What if an admin needs to bypass these scopes? A: You can add a check inside your scope: if ($user->isAdmin()) return $query;. Just be explicit and keep that logic centralized.

Q: Is this enough to stop all IDOR? A: It's a massive step forward, but you still need to ensure your API endpoints are protected by middleware and that your route parameters are properly validated.

Final Thoughts

I’m still experimenting with using custom collection classes to enforce these checks automatically upon retrieval, but for now, the scoped query approach is the most reliable pattern I’ve found. It’s readable, it’s testable, and most importantly, it makes it incredibly difficult for a developer to accidentally introduce an IDOR vulnerability during a late-night hotfix.

Don't rely on your memory to check permissions. Make the database do the heavy lifting for you.

Back to Blog

Similar Posts

Close-up of Scrabble tiles spelling 'data breach' on a blurred background
SecurityJune 21, 20265 min read

Preventing Mass Assignment Vulnerabilities with DTOs in Laravel and Express

Stop mass assignment vulnerabilities by using DTOs to decouple input from your database. Learn how to secure your Laravel and Express apps with strict validation.

Read more
A close-up of CDs and disks on a desk, featuring hands in a tech environment.
Security
June 21, 2026
4 min read

Secure file uploads from the ground up: A developer's guide

Secure file uploads from the ground up require more than basic validation. Learn how to prevent RCE and directory traversal in your production systems.

Read more
Close-up of colorful programming code displayed on a computer screen, showcasing modern coding concepts.
SecurityJune 20, 20264 min read

XSS prevention strategies: A guide for modern web developers

XSS prevention strategies are essential for securing modern web apps. Learn to identify the three main variants and implement robust, layered defenses today.

Read more