Home • Articles • AI delivery • How do you secure a Laravel API before production?

How do you secure a Laravel API before production?

Eliyce editorial cover for how do you secure a laravel api before production?

TL;DR

Protect each request at three separate boundaries: who may call it, which values may enter the model, and how often the action may run. Start with one real change and make the evidence visible beside the work. Check authorization policy, request validation and explicit fillable fields, endpoint-specific rate limits. If a reviewer still has to guess about scope, proof, or ownership, pause and repair that gap before scaling the practice. The result should be a workflow another qualified person can follow and challenge without relying on private chat

In this article
  1. Check 1 — authorization vs authentication confusion
  2. Check 2 — mass assignment and fillable
  3. Check 3 — rate limiting on sensitive endpoints
  4. Use a four-part evidence check
  5. Frequently Asked Questions

8 min read

The public wording behind this refresh can be seen in the question and discussion that surfaced the same practical uncertainty. It is a question source, not proof of the recommendation; the operational answer below is checked against the article’s authoritative sources.

Protect each request at three separate boundaries: who may call it, which values may enter the model, and how often the action may run. The useful question is not whether the team can produce an impressive output once. It is whether the work remains understandable, reviewable, and safe when another person follows it under normal delivery pressure.

Check 1 — authorization vs authentication confusion

The fastest way to find broken access control is to read every write endpoint and ask: “Where is the policy check?”

This is weak:

public function update(UpdateProjectRequest $request, Project $project): ProjectResource
{
    $project->update($request->validated());

    return new ProjectResource($project);
}

The request may be valid. The user may be logged in. Neither proves the user owns the project or has the right role inside the team.

Make the authorization boundary explicit:

public function update(UpdateProjectRequest $request, Project $project): ProjectResource
{
    $this->authorize('update', $project);

    $project->update($request->validated());

    return new ProjectResource($project);
}

Then put the rule where Laravel expects it:

public function update(User $user, Project $project): bool
{
    return $project->team
        ->members()
        ->whereKey($user->id)
        ->wherePivot('role', 'owner')
        ->exists();
}

You can also authorize inside a Form Request, but keep the decision tied to the model and action. A request class that returns true from authorize() because “the route already has auth middleware” is not doing authorization. It is documenting a gap.

For API tokens, token abilities are useful, but they do not replace resource authorization. A token with project:update still needs to be checked against the specific project being updated. Scopes say what kind of action the token may attempt. Policies decide whether this action is allowed on this record.

Audit shortcut: search for ->update(, ::create(, delete(, and custom action methods in API controllers. For each write path, find the matching $this->authorize(...), policy middleware, gate, or Form Request authorization. If you cannot point to it, assume the endpoint is under-authorized until proven otherwise.

Check 2 — mass assignment and fillable

Mass assignment bugs usually arrive through convenience:

public function store(Request $request): UserResource
{
    $user = User::create($request->all());

    return new UserResource($user);
}

If the model allows more fields than the endpoint should accept, the client gets to write fields you did not intend to expose. Laravel’s own docs use is_admin as the classic example: an unexpected request field reaches create() and changes a database column.

The fix is not only “add $fillable“. The fix is to make the input boundary and model boundary agree.

Use a Form Request:

public function rules(): array
{
    return [
        'name' => ['required', 'string', 'max:120'],
        'email' => ['required', 'email'],
    ];
}

Then pass validated data, not the whole request:

public function store(StoreUserRequest $request): UserResource
{
    $user = User::create($request->safe()->only(['name', 'email']));

    return new UserResource($user);
}

On the model, prefer a narrow $fillable list for fields that clients may mass assign:

protected $fillable = [
    'name',
    'email',
];

Be careful with $guarded = []. Laravel supports it, but the docs are explicit that if you unguard a model, you should hand-craft the arrays passed to fill, create, and update. That can be acceptable in a disciplined codebase. It is a bad default for a team where controllers vary in quality.

Also check server-owned fields. team_id, user_id, role, status, is_admin, approved_at, and billing fields often should not come from the client at all. Set them from server context:

$project = $request->user()->team->projects()->create([
    ...$request->safe()->only(['name', 'description']),
    'status' => ProjectStatus::Draft,
]);

Audit shortcut: search for $request->all(), all() inside controller writes, forceFill, unguard, and $guarded = []. Then compare every fillable model field against the endpoints that write to it.

Check 3 — rate limiting on sensitive endpoints

Default API throttling is useful, but sensitive endpoints need their own limits.

The question is not “does the app have a throttle somewhere?” The question is “does this action have a limit that matches the damage it can do?”

Login, password reset, email verification, invitation acceptance, token creation, export generation, coupon validation, and availability checks deserve separate attention. They may be cheap in normal use and expensive under abuse. They may also reveal whether an account, email address, invite token, or workspace slug exists.

Laravel lets you define named limiters:

RateLimiter::for('sensitive-api', function (Request $request) {
    $userKey = $request->user()
        ? 'user:'.$request->user()->id
        : 'guest:'.$request->ip();

    return [
        Limit::perMinute(30)->by($request->ip()),
        Limit::perMinute(5)->by($userKey),
    ];
});

Then attach the limiter to the routes that need it:

Route::middleware(['auth:sanctum', 'throttle:sensitive-api'])->group(function () {
    Route::post('/tokens', [ApiTokenController::class, 'store']);
    Route::post('/exports', [ExportController::class, 'store']);
});

For guest routes, segment by the thing being attacked where that makes sense. A password reset route often needs an IP limit and an email-based limit. Laravel supports multiple limits per limiter; use that to stop one IP from spraying many accounts and one account from being hammered through many IPs.

Rate limits are not a replacement for authorization, validation, logging, or alerting. They are a pressure valve. Without them, a small logic mistake becomes easier to exploit at volume.

Audit shortcut: list every route that creates tokens, sends email, checks availability, triggers exports, changes credentials, or touches billing. If it uses the same throttle as harmless reads, give it a named limiter.

Audit this first in an existing app

Start with write routes. Read the controller, request class, model, and route middleware together. For each endpoint, answer three questions in order: who can do this to this specific record, which exact fields can the client write, and how hard can this endpoint be pushed before Laravel returns 429?

That is the practical Laravel API security review. Not a giant spreadsheet. Not a theoretical threat model before you have read the code. Three checks, applied route by route, until the risky endpoints stop hiding behind framework defaults.

What next?

If you want a senior review of the endpoints that matter, Get the Architecture Review.

Use a four-part evidence check

The practical test is whether another qualified person can understand the work without the original operator narrating it. For how do you secure a laravel api before production?, inspect four things together. A detailed policy with no repository evidence is equally weak.

Review surfaceQuestion to askIf the answer is unclear
Authorization policyCan the team point to it without reconstructing the story from chat?Write it down beside the work.
Request validation and explicit fillable fieldsIs the boundary explicit enough that two reviewers would reach the same starting point?Narrow the scope before continuing.
Endpoint-specific rate limitsDoes the evidence show what ran, what changed, and what remains unknown?Retain the result as a review artifact.

This worksheet is intentionally small enough to use on a real change. Start with one recent item that felt slow, risky, or surprisingly hard to review. Follow it from the original request to its present state. Record where a reviewer had to guess, where evidence disappeared, and which decision arrived late. The gaps show whether the next improvement belongs in documentation, automation, planning, review capacity, or ownership.

For example, imagine that the final code looks plausible and the tests are green, but nobody can connect the change to an accepted requirement. More testing will not repair that missing intent. The team must recover the requirement, decide whether the implementation still fits it, and preserve that link for the next reviewer. The same logic applies when the request is clear but the executed checks, risk boundary, or approval are missing.

Use one stop rule: if the person responsible for the outcome cannot explain what evidence supports the next step, pause before widening the change. Protect each request at three separate boundaries: who may call it, which values may enter the model, and how often the action may run. This keeps speed connected to a decision the team can defend.

A branded decision diagram showing the four evidence checks for how do you secure a laravel api before production?
Use the four checks on one real change before applying the practice across the team.
Eliyce software professionals reviewing the evidence and ownership for how do you secure a laravel api before production?
A second reviewer should be able to follow the decision without the original operator narrating it.

For adjacent decisions, use the Eliyce guides on how to handle production ready ai code and how to handle ai generated code review.

If this problem is affecting live delivery, Eliyce can help you turn the diagnosis into a bounded software build change with explicit evidence and decision points. See the relevant Eliyce service.

Frequently Asked Questions

How do you secure a Laravel API before production?

Protect each request at three separate boundaries: who may call it, which values may enter the model, and how often the action may run. Apply it to one real change first, then keep the evidence beside that change so another reviewer can follow the reasoning.

How do I stop users changing fields they should not control in Laravel?

Start with authorization policy and request validation and explicit fillable fields. Together they show whether the work has a clear boundary and whether the team can evaluate it without relying on memory.

Which Laravel API endpoints need their own rate limit?

Give separate limits to login, password reset, search, exports, uploads, and any action that is expensive or changes important data. A single global limit hides the difference between harmless reads and endpoints an attacker can abuse.

Is Form Request validation enough to prevent mass assignment?

No. Validation decides which input shapes are acceptable; authorization decides whether this caller may make the change; an explicit model allowlist decides which attributes may be written. Keep all three boundaries because each blocks a different failure.

How should an authorization test prove that object access is safe?

Test one user trying to read or change another user’s record, plus each meaningful role boundary. A test that only checks an unauthenticated request misses the common case where a valid account reaches data it does not own.

What should a Laravel API log without exposing secrets?

Record the route, request or trace ID, authenticated actor ID, decision outcome, and throttle or policy failure. Exclude tokens, passwords, session values, and sensitive request bodies so the security log does not become a second data leak.

Written by

Haider Lasani

Has built and maintained real systems since 2008 and writes for engineering leaders whose teams ship with AI. Author of The Decisions AI Can’t Make for You and The Backlog That Thinks.

About the author

Recognise this in your own team?

Eliyce helps in three ways: your engineers deliver with AI inside one enforced process in the AI Delivery Programme, your business agents are built and managed like real software, and Spine shows what shipped and how it was checked.