What is security by design?
Security by design means you do not treat security as a standalone feature bolted on at the end of a project, but as a design principle woven into every architecture decision. From the first database migration, the first route, the first form. It is the difference between building a house with a strong lock on the door and building a house that is engineered to be break-in resistant from the foundation up.
In practice, this means asking yourself with every feature: who is allowed to see this? What happens if someone sends malicious input? What data leaks if this endpoint becomes public? Security by design is not extra work — it is the right way of working. And it saves you enormous cost compared to patching vulnerabilities after the fact.
Adding security after the fact is like installing a seatbelt after the car has already crashed. Security by design builds the airbags in before the first test drive.
Threat modeling: knowing where you are vulnerable
Security by design does not start with code — it starts with thinking. Threat modeling is the process of systematically mapping where your application can be attacked. What data is valuable? Who are potential attackers? What are the consequences if something goes wrong?
At Coding Agency Meppel, we run this process at the start of every project. We identify the trust boundaries — the points where data moves from one context to another. User input that lands in the database, API responses sent to a frontend, webhooks arriving from external services. At each of those boundaries, you must validate, sanitize, and authorize.
A concrete example: a SaaS platform with multi-tenancy. The threat model immediately identifies the risk that tenant A could access tenant B's data. That risk then drives the architecture: global scopes on Eloquent models, middleware that enforces tenant context, and tests that explicitly attempt cross-tenant access.
Laravel's security layers
Laravel is not our framework of choice by accident. It provides multiple security layers that — when applied correctly — form a solid foundation. But a framework only protects you if you understand what it does and where its limits lie.
Authorization as architecture
Laravel's Gate and Policy system makes it possible to centralize authorization logic. Instead of ad-hoc if-statements in controllers, you define Policies per model. Every action — view, create, update, delete — gets an explicit authorization check. This prevents the most common vulnerability from the OWASP Top 10: Broken Access Control.
The principle is simple: deny by default. Nothing is accessible unless you explicitly permit it. A new endpoint without a Policy? Blocked. This is the opposite of the common mistake where everything is open by default and you hope you do not forget to lock something down.
Input validation as the first line of defense
Every incoming request is potentially hostile. Laravel's Form Request classes provide an elegant way to centralize validation and enforce it before your controller logic ever runs. You define not only what is required, but also data types, acceptable values, and valid relationships.
A well-designed validation layer goes beyond required|string. It is: required|string|max:255|not_regex:/[<>]/. It is exists:teams,id combined with a scope that verifies the user is actually a member of that team. Input validation is not merely a UX feature — it is a security measure.
Mass assignment protection
Laravel protects against mass assignment by default — the unintended assignment of fields via request data. With $fillable or $guarded on your Eloquent models, you control exactly which fields can be modified through forms or APIs. Without this protection, an attacker could send extra fields — such as is_admin=1 — and escalate privileges.
Encryption and hashing
Sensitive data belongs encrypted at rest. Laravel provides two mechanisms. Hashing for passwords: bcrypt or Argon2, irreversible, with automatic salting. Encryption for data you need to read later: AES-256-CBC via Laravel's encrypt() and decrypt() helpers, or the encrypted cast on Eloquent attributes.
The distinction is critical. Passwords are hashed — you compare hashes, never storing the original. Personal data, API tokens, and session data are encrypted — you can read them back, but only with the correct key.
Common mistakes and how to prevent them
Security by design is not only about what you do, but also about what you avoid. These are the mistakes we encounter most frequently when reviewing or taking over codebases:
- Route model binding without scoping. Laravel's route model binding is convenient, but without scope a user can access other people's records by guessing URL parameters. Always use scoped bindings or add a Policy check.
- Raw queries without parameter binding. Eloquent protects against SQL injection by default, but the moment you use
DB::raw()orwhereRaw()without parameter binding, you discard that protection. - Sensitive data in logs. Laravel logs exceptions by default, but if an exception contains request data with passwords or tokens, that data ends up in your log files. Always configure which fields are excluded.
- API responses that return too much. A
User::all()in an API response sends every column — including password hashes, remember tokens, and internal fields. Always use API Resources to explicitly control which fields are visible. - Missing rate limiting. Without rate limiting on login endpoints, password resets, and APIs, your application is vulnerable to brute-force attacks. Laravel's
RateLimitermakes this trivial to implement. - Debug mode in production. An
APP_DEBUG=truein production reveals stack traces, database configuration, and environment variables to every visitor.
Security headers and browser protection
A secure application sends the right instructions to the browser. Security headers tell the browser how to treat your application and which risks to block. This is a defense layer that is often forgotten but effectively prevents many common attacks.
Content Security Policy (CSP) determines which sources the browser is allowed to load. No inline scripts, no external fonts from unknown domains, no third-party iframes. This is your most powerful weapon against XSS attacks.
Strict-Transport-Security (HSTS) enforces HTTPS and prevents the browser from ever making an unencrypted connection. X-Content-Type-Options prevents MIME sniffing. X-Frame-Options protects against clickjacking by preventing your application from loading in an iframe.
Dependency management as a security measure
Your application is only as secure as your weakest dependency. An outdated package with a known vulnerability is an open door for attackers. Security by design means actively managing your dependencies.
We use composer audit and npm audit as part of our CI/CD pipeline. Every build checks for known vulnerabilities in installed packages. On a critical vulnerability, the build is blocked until the dependency is updated.
Additionally, we follow the principle of minimal dependencies. Every package you add expands your attack surface. Before adding a dependency, we ask: can we solve this ourselves in a few lines? Is this package actively maintained? Does it have a solid security track record?
Security by design is not a checklist you work through. It is a mindset you apply to every line of code you write.
Want to know how Coding Agency Meppel applies security by design to your project? Or would you like an existing application reviewed for security risks? Get in touch for a no-obligation conversation.