---
title: "Laravel security middleware: quickstart · camada"
description: "Keep credential stuffers, scrapers and exploit scanners out of your Laravel app: install from GitHub, two lines and one env var."
url: https://www.camada.app/docs/quickstart/laravel
last_updated: 2026-10-30
---

Quickstart

# Laravel quickstart

Installed from GitHub ([camada-app/camada-php](https://github.com/camada-app/camada-php)).

## 1. Install

Not on Packagist yet. Run these in your project's folder: Composer installs it straight from GitHub, composer.lock pins the commit, and composer update camada/camada moves it.

```sh
composer config repositories.camada vcs https://github.com/camada-app/camada-php
composer require camada/camada:dev-main
```

## 2. Environment

The key is printed once when you create a project in the app. Laravel loads .env into the environment, except once php artisan config:cache has run: there, set them as real environment variables.

.env

```dotenv
CAMADA_KEY=<ingest_token>.<snap_token>
CAMADA_INGEST_URL=http://localhost:8787   # dev only; defaults to production ingest
```

## 3. Wire it in

```php
// bootstrap/app.php (Laravel 11+), inside the existing ->withMiddleware(function (Middleware $middleware) { … })
$middleware->prepend(\Camada\Laravel\Middleware::class);   // first in the global stack

// or app/Http/Kernel.php (Laravel 10): the first entry of protected $middleware = [ … ]
\Camada\Laravel\Middleware::class,
```

- Prepend it so blocked requests never reach the rest of the stack.
- terminate() is the post-response phase: the spool ships and the snapshot refreshes after the kernel has sent the response.

## 4. Add the beacon to HTML pages

Optional but recommended. The beacon cuts false positives by showing which sessions are real browsers. Use the helper.

```php
// routes/web.php
use Camada\Camada;
use Illuminate\Http\Request;

Route::get('/', function (Request $request) {
    $ctx = $request->attributes->get('camada');   // the middleware put it there
    return '<html><head>' . Camada::default()->scriptTag($ctx) . '</head><body>…</body></html>';
});
```

## 5. Send app outcomes

Login failures, signups and checkout declines are the signals no edge position sees. The identifier is HMAC-hashed in your process. How they catch [credential stuffing](https://www.camada.app/use-cases/credential-stuffing).

```php
// in a route or controller, once your own sign-in check fails ($email: the account it was for)
\Camada\Camada::default()->track($request->attributes->get('camada'), 'login_failed', $email);
```

Runtime

## What it does per request

1. Keeps the snapshot in a file-backed cache every worker shares (CAMADA_CACHE_DIR) and refreshes it in the post-response phase, after the client has its bytes. An empty cache answers the first request open.
2. Resolves the client IP from REMOTE_ADDR per your trusted-proxy setting. Raw X-Forwarded-For is never trusted without it; behind nginx real_ip the peer is already the client, so say none.
3. Your ordered custom rules first, then allow, block, challenge. A block gets a 403 with x-block-reason before your app runs; a challenge serves the proof-of-work page on a page load, and an API call reaches your app with an x-camada-challenge: 1 response header. The event still ships.
4. Serves /_cam/b.js (the beacon, first-party, from opcache) and relays /_cam/fp posts onto the event spool with the resolved client IP.
5. Sets x-rid and the _sfp session cookie, and after the response has ended appends one redacted event to the spool; a due spool ships in that same post-response phase.

Limits

## Caveats at this position

- Enforcement at this position covers IP, path, user-agent and request-header conditions. ASN, country and TLS-fingerprint entries fail open in-app.
- Header order is whatever getallheaders() yields, so the analyst reads no HEADER_ORDER signal from this tap and never scores its absence.
- Under FPM and FrankenPHP the post-response work runs after fastcgi_finish_request(); under php -S and mod_php the buffer is flushed with a Content-Length first. Run the CLI server with PHP_CLI_SERVER_WORKERS=4 or more.
- The cache dir must be a local, writable directory (flock and rename are the concurrency model). Each host keeps its own copy and ships its own spool.
- Events ship when the spool is due, on the next request that finds it due: a quiet site ships its last events on its next hit, not on a timer.

Kill switch: `CAMADA_DISABLED=1`, checked per request. All other variables are listed in the [docs index](https://www.camada.app/docs); the package's options and deployment notes are on the [PHP page](https://www.camada.app/docs/php).

## Sitemap

[Every page on this site](https://www.camada.app/sitemap.md)
