PHPackages                             imran/laravel-watchtower - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. imran/laravel-watchtower

ActiveLibrary[Security](/categories/security)

imran/laravel-watchtower
========================

Intelligent Adaptive Rate Limiter &amp; Security Shield for Laravel - Advanced threat detection, behavioral analysis, fingerprinting, and auto-hardening protection.

v1.0.0(1mo ago)00MITPHPPHP ^8.1|^8.2|^8.3|^8.4CI failing

Since Jul 18Pushed 1mo agoCompare

[ Source](https://github.com/grim-reapper/laravel-watchtower)[ Packagist](https://packagist.org/packages/imran/laravel-watchtower)[ Docs](https://github.com/grim-reapper/laravel-watchtower)[ RSS](/packages/imran-laravel-watchtower/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependencies (12)Versions (2)Used By (0)

🛡️ Laravel Watchtower
=====================

[](#️-laravel-watchtower)

[![Tests](https://github.com/grim-reapper/laravel-watchtower/actions/workflows/tests.yml/badge.svg)](https://github.com/grim-reapper/laravel-watchtower/actions/workflows/tests.yml)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)

**Intelligent Adaptive Rate Limiter &amp; Security Shield for Laravel**

Laravel Watchtower is a production-ready security package that goes far beyond Laravel's default rate limiting. It acts as a lightweight application firewall, providing adaptive rate limiting, behavioral analysis, fingerprinting, threat scoring, bot/scraper detection, auto-hardening, and progressive penalties.

---

🚀 Features
----------

[](#-features)

### Core Features

[](#core-features)

FeatureDescription✅ **Adaptive Rate Limiting**Dynamic limits that change based on threat score✅ **User Fingerprinting**Multi-factor fingerprinting beyond IP tracking✅ **Threat Scoring System**Real-time risk assessment for every request✅ **Behavioral Analysis**Timing patterns, navigation flow, entropy analysis✅ **Bot &amp; Scraper Detection**Headless browser detection, header analysis✅ **Auto-Hardening**Automatically strengthens defenses during attacks✅ **Progressive Penalties**Escalating consequences: slowdown → throttle → block✅ **Honey Endpoints**Trap endpoints that detect scanners and bots✅ **Distributed Attack Detection**Detect coordinated attacks across multiple sources✅ **Endpoint Sensitivity Profiles**Different protection levels for different routes### Threats It Solves

[](#threats-it-solves)

- Credential stuffing &amp; brute force attacks
- Web scraping &amp; API abuse
- Bot traffic &amp; headless automation
- Session abuse &amp; rotating IP attacks
- Endpoint probing &amp; account enumeration
- DDoS &amp; burst attacks

---

📋 Requirements
--------------

[](#-requirements)

- PHP 8.1, 8.2, 8.3, or 8.4
- Laravel 10.x, 11.x, 12.x, or 13.x
- Redis (strongly recommended for production)
- Composer

---

🔧 Installation
--------------

[](#-installation)

```
composer require imran/laravel-watchtower
```

### Quick Setup

[](#quick-setup)

```
php artisan watchtower:install
php artisan migrate
```

This will publish the config, migrations, views, and language files.

### Manual Setup

[](#manual-setup)

Publish configuration:

```
php artisan vendor:publish --tag=watchtower-config
php artisan vendor:publish --tag=watchtower-migrations
php artisan migrate
```

---

⚙️ Configuration
----------------

[](#️-configuration)

Configure via `config/watchtower.php` or environment variables:

```
WATCHTOWER_STORAGE=redis
WATCHTOWER_REDIS_CONNECTION=default
WATCHTOWER_KEY_PREFIX=watchtower
WATCHTOWER_ADAPTIVE_ENABLED=true
WATCHTOWER_BOT_DETECTION=true
WATCHTOWER_BEHAVIOR_ENABLED=true
WATCHTOWER_FINGERPRINT_ENABLED=true
WATCHTOWER_AUTO_HARDEN=true
WATCHTOWER_HONEY_ENABLED=true
WATCHTOWER_BLOCK_MESSAGE=Access denied by security system.
```

### Key Configuration Sections

[](#key-configuration-sections)

- **`profiles`** - Define sensitivity profiles for different endpoints
- **`penalties`** - Progressive penalty levels and durations
- **`adaptive`** - Dynamic limit adjustment settings
- **`threat_scoring`** - Score thresholds and decay settings
- **`behavior`** - Behavioral analysis configuration
- **`bot_detection`** - Bot detection rules and patterns
- **`auto_hardening`** - Auto-hardening behavior
- **`honey_endpoints`** - Fake endpoints for bot trapping
- **`fingerprint`** - Fingerprinting components
- **`exempt`** - Paths and IPs that bypass protection

---

🎯 Usage
-------

[](#-usage)

### Middleware

[](#middleware)

**Watchtower protects your app automatically, out of the box.** When the package boots, it pushes itself (with the `default` profile) onto both the `web` and `api` middleware groups — every route already gets baseline adaptive rate limiting with no setup required. You only need to reach for explicit middleware below when you want a **different profile** than `default` for specific routes (e.g. tighter limits on `/login`).

Protect a route group with a specific profile:

```
Route::middleware('watchtower:login')->group(function () {
    Route::post('/login', [AuthController::class, 'login']);
    Route::post('/register', [AuthController::class, 'register']);
});
```

With profile-specific protection:

```
Route::middleware('watchtower:login')->post('/login', [AuthController::class, 'login']);
Route::middleware('watchtower:api')->prefix('api')->group(function () {
    Route::get('/users', [UserController::class, 'index']);
});
Route::middleware('watchtower:critical')->post('/password/reset', [AuthController::class, 'resetPassword']);
Route::middleware('watchtower:scrape-sensitive')->get('/search', [SearchController::class, 'search']);
```

### Fluent API

[](#fluent-api)

```
use Imran\Watchtower\Facades\Watchtower;

// Basic protection
Watchtower::protect('login')
    ->adaptive()
    ->detectBots()
    ->trackFingerprint()
    ->autoHarden()
    ->captchaAfter(3)
    ->banAfter(10);
```

### Facade Methods

[](#facade-methods)

```
// Fluent protection
Watchtower::protect('api')->adaptive()->detectBots();

// Process a request manually
$decision = Watchtower::process($request, 'login');

// Check if an identifier is blocked
$isBlocked = watchtower_is_blocked($identifier);

// Get protection headers
$headers = Watchtower::getHeaders();

// Get the last decision
$decision = Watchtower::getDecision();
```

### CAPTCHA / Challenge configuration

[](#captcha--challenge-configuration)

When `captchaAfter(N)` (or a profile's `force_captcha_after`) is triggered, Watchtower serves a challenge page at `/_watchtower/challenge`. Configure a real CAPTCHA provider so it's actually enforced server-side:

```
WATCHTOWER_CAPTCHA_PROVIDER=turnstile   # turnstile | recaptcha | hcaptcha
WATCHTOWER_CAPTCHA_SITE_KEY=your-site-key
WATCHTOWER_CAPTCHA_SECRET_KEY=your-secret-key
```

If no site/secret key is configured, Watchtower automatically falls back to a **server-verified cooldown** challenge (`WATCHTOWER_CHALLENGE_COOLDOWN`, default 15s) instead of pretending to verify a CAPTCHA that isn't set up. The wait is timed from a server-side timestamp, not a client-supplied value, so it can't be bypassed by submitting the form immediately.

A verified pass is remembered per-identifier for `WATCHTOWER_CHALLENGE_PASS_TTL` minutes (default 30), so a legitimate user who already proved they're human isn't forced through the challenge again on every request. Watchtower's own `/_watchtower/*` routes are always exempt from its own middleware, so a blocked or challenged identifier can never be locked out of the one page that lets them resolve it.

### Decoupled frontends (Next.js, React, Angular, Vue, mobile apps)

[](#decoupled-frontends-nextjs-react-angular-vue-mobile-apps)

If your frontend calls the Laravel app as a JSON API rather than navigating full pages (a Next.js/React/Angular/Vue SPA, or a mobile app), send `Accept: application/json`(most HTTP clients — `fetch`, `axios`, etc. — already do this) and Watchtower responds with structured JSON instead of an HTML page or an HTTP redirect:

**A protected endpoint returns `428 Precondition Required` when a challenge is triggered:**

```
// HTTP 428
{
  "error": "Verification required before this request can proceed.",
  "code": "CHALLENGE_REQUIRED",
  "challenge": {
    "provider": "turnstile",           // or "recaptcha" | "hcaptcha"
    "site_key": "your-site-key",
    "captcha_configured": true,        // false => no CAPTCHA keys set, cooldown mode
    "cooldown_seconds": 15,
    "challenge_token": "eyJ...",       // opaque, echo this back on verify
    "verify_url": "https://yourapp.com/_watchtower/challenge/verify"
  }
}
```

**POST the solved CAPTCHA token (or, if `captcha_configured` is false, just wait `cooldown_seconds` and POST anyway) to `verify_url` as JSON:**

```
// POST /_watchtower/challenge/verify
// { "challenge_token": "eyJ...", "captcha_token": "" }

// 200 OK
{ "passed": true }

// or 422 Unprocessable Entity
{ "passed": false, "errors": { "captcha": "Verification failed. Please try again." } }
```

On `passed: true`, retry your original request — the identifier is now exempt from being re-challenged for `WATCHTOWER_CHALLENGE_PASS_TTL` minutes. This endpoint does **not** require a CSRF token or a cookie session: `challenge_token` is a short-lived, server-signed token (`WATCHTOWER_CHALLENGE_TOKEN_TTL` minutes, default 10) that proves the server actually issued this specific challenge to this specific identifier — it's what makes the flow work for stateless clients (Bearer-token auth, a frontend on a different origin, no cookies at all), not just cookie-based browser sessions.

**Two things to set up on your own side for a fully decoupled (cross-origin) deployment:**

1. If your frontend needs to read Watchtower's response headers (`X-Watchtower-Score`, `X-Watchtower-Level`, etc. — only sent if `WATCHTOWER_RESPONSE_HEADERS=true`) via `fetch()`/ `axios` on a **different origin** than your API, add them to your own CORS config (`config/cors.php`): `'exposed_headers' => ['X-Watchtower-Action', 'X-Watchtower-Score', 'X-Watchtower-Level']`. The JSON `428`/`429` response bodies already carry this same information, so this is optional — only needed if you specifically want to read it from headers.
2. CORS preflight (`OPTIONS`) requests are automatically exempt from all scoring/rate-limiting, so they don't silently cost your frontend part of its request budget.

**Minimal framework-agnostic example** (React, Next.js, Angular, or plain JS — same shape, just swap in your CAPTCHA widget's render call):

```
async function callProtectedApi(url, options = {}) {
  const response = await fetch(url, {
    ...options,
    headers: { ...options.headers, Accept: 'application/json' },
  });

  if (response.status !== 428) {
    return response; // not challenged — handle normally
  }

  const { challenge } = await response.json();

  const captchaToken = challenge.captcha_configured
    ? await renderCaptchaAndWaitForToken(challenge) // your widget's render+resolve call
    : await new Promise((resolve) => setTimeout(resolve, challenge.cooldown_seconds * 1000));

  const verifyResponse = await fetch(challenge.verify_url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify({
      challenge_token: challenge.challenge_token,
      captcha_token: captchaToken || '',
    }),
  });

  const { passed } = await verifyResponse.json();
  if (!passed) {
    throw new Error('Challenge verification failed');
  }

  return callProtectedApi(url, options); // retry the original request
}
```

A React hook is just this wrapped in `useCallback`/state for the CAPTCHA-widget-open UI state — the request/response contract above is all a framework binding needs.

### Helper Functions

[](#helper-functions)

```
// Get Watchtower instance
$watchtower = watchtower();

// Create a protected route
watchtower('login')->adaptive()->detectBots();

// Check if identifier is blocked
if (watchtower_is_blocked($identifier)) {
    // Handle blocked user
}
```

---

📊 Endpoint Profiles
-------------------

[](#-endpoint-profiles)

ProfileUse CaseBase LimitBot DetectionFingerprintAuto-Harden`default`General endpoints30/min✅✅❌`critical`Authentication, PW reset10/min✅✅✅`login`Login endpoints5/min✅✅✅`api`API routes120/min❌✅❌`scrape-sensitive`Search, data endpoints30/min✅✅✅---

🧠 Architecture
--------------

[](#-architecture)

```
Incoming Request
       ↓
Request Analyzer          → Exempt check, honey endpoints
       ↓
Fingerprint Engine        → Multi-factor device fingerprinting
       ↓
Behavior Engine           → Timing analysis, entropy, navigation
       ↓
Threat Scoring System     → Dynamic risk assessment (0-100+)
       ↓
Adaptive Decision Engine   → Dynamic limit adjustment
       ↓
Action Layer              → Allow / Slowdown / Challenge / Block / Harden
       ↓
Response                  → Headers, delay, block page, challenge view

```

---

📝 Events
--------

[](#-events)

Watchtower dispatches events you can listen for:

```
// EventServiceProvider.php
protected $listen = [
    \Imran\Watchtower\Events\ThreatDetected::class => [
        YourListener::class,
    ],
    \Imran\Watchtower\Events\BotDetected::class => [
        YourBotListener::class,
    ],
    \Imran\Watchtower\Events\AttackPatternDetected::class => [
        // Fired for scraper, account-enumeration, distributed-attack, and probing detections
        YourPatternListener::class,
    ],
    \Imran\Watchtower\Events\EndpointHardened::class => [
        YourHardeningListener::class,
    ],
];
```

Each dispatch is individually toggleable via the `events.dispatch_*` config keys.

---

🛠️ Artisan Commands
-------------------

[](#️-artisan-commands)

```
# Install Watchtower
php artisan watchtower:install

# Check status of an identifier
php artisan watchtower:status {identifier?}

# Unblock an identifier
php artisan watchtower:unblock {identifier}

# Clear all data for an identifier
php artisan watchtower:clear {identifier}

# Delete audit-log rows older than privacy.data_retention_days
# (self-scheduled daily unless privacy.auto_prune is disabled)
php artisan watchtower:prune [--days=N]
```

---

📦 Storage
---------

[](#-storage)

Watchtower supports three storage drivers:

DriverBest ForNotes**Redis** 🏆ProductionFastest, supports lists/expiry for behavioral data**Cache**DevelopmentWorks with any Laravel cache driver**Database**No Redis availableFast counters (score/requests/violations/etc.) stored in the `watchtower_cache` table insteadIndependent of which storage driver above is active, `logging.log_to_database` (on by default) persists an audit trail — every scored detection and every block — to the `watchtower_security_events` and `watchtower_blocked_entities` tables. This is always a real SQL database connection, separate from the fast-storage driver choice.

Representative sample of the fast-storage keys (driver-prefixed, e.g. `watchtower:score:{id}`):

```
requests:{id}          → Request count (sliding window)
score:{id}              → Current threat score
violations:{id}         → Progressive penalty count
blocked:{id}             → Block status with TTL
fingerprint:{id}        → Fingerprint metadata
behavior:{id}:{type}    → Behavioral data
nav:{id}                 → Navigation history (list)
timings:{id}             → Request timing data (list)
hardened:{id}            → Auto-hardening status (identifier-level)
hardened_route:{route}  → Auto-hardening status (route-wide)
challenge_passed:{id}   → Verified-challenge exemption
challenge_token issuance/cooldown/snapshot keys for the challenge flow

```

---

🔒 Privacy
---------

[](#-privacy)

Watchtower respects user privacy with configurable options:

```
// config/watchtower.php
'privacy' => [
    'anonymize_ip' => true,        // Zero the last two IPv4 octets (or last 80 bits of IPv6)
                                    // before it's ever hashed, stored, or logged
    'hash_fingerprint' => true,    // SHA-256 all fingerprint data
    'log_full_headers' => false,   // Reserved — no code path logs full headers regardless
    'data_retention_days' => 90,   // Rows older than this are deleted by watchtower:prune
    'auto_prune' => true,          // Self-schedule watchtower:prune to run daily
],
```

---

📈 Dashboard (Coming in v2)
--------------------------

[](#-dashboard-coming-in-v2)

- Live attack map
- Real-time threat feed
- Blocked users management
- Endpoint heatmaps
- Attack timelines
- Suspicious fingerprint explorer
- Scraping analytics

---

🔌 Integrations (Planned)
------------------------

[](#-integrations-planned)

- **Cloudflare** - Sync bans and rules
- **Laravel Pulse** - Security metrics
- **Laravel Telescope** - Threat inspection
- **Laravel Horizon** - Queue protection
- **Sanctum / Passport** - API token intelligence

---

🧪 Testing
---------

[](#-testing)

```
composer test
```

---

⚠️ Important Notes
------------------

[](#️-important-notes)

1. **Redis is strongly recommended** for production. The behavior analysis engine relies on Redis list operations for timing and navigation history.
2. **False positives** are minimized through configurable scoring weights, score decay, and trust levels.
3. **Performance** is optimized with Redis and log sampling (`logging.sample_rate`) for high-traffic sites.
4. **Data protection tooling** — anonymized IPs, hashed fingerprints, and enforced data retention (`watchtower:prune`) are built in to help with GDPR/privacy compliance; whether your overall deployment is compliant depends on your full data-handling practices, not this package alone.

---

📄 License
---------

[](#-license)

The MIT License (MIT). See [LICENSE](LICENSE) for more information.

---

🏗️ Built With
-------------

[](#️-built-with)

- Laravel 10.x – 13.x
- Redis
- PHP 8.1 – 8.4
- SHA-256 fingerprinting
- Statistical behavioral analysis

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance90

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

45d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/7252105628ffa6f064585370161626c7c72e68dd2e74ee66aa50d5894543c183?d=identicon)[webz2feel](/maintainers/webz2feel)

---

Top Contributors

[![grim-reapper](https://avatars.githubusercontent.com/u/7957389?v=4)](https://github.com/grim-reapper "grim-reapper (3 commits)")

---

Tags

laravelsecuritycaptcharate-limiterfirewallthrottlethreat-detectionwaffingerprintingbot-detection

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/imran-laravel-watchtower/health.svg)

```
[![Health](https://phpackages.com/badges/imran-laravel-watchtower/health.svg)](https://phpackages.com/packages/imran-laravel-watchtower)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3365.5M359](/packages/psalm-plugin-laravel)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k17.6M172](/packages/laravel-pulse)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)[spatie/laravel-health

Monitor the health of a Laravel application

89313.5M196](/packages/spatie-laravel-health)[laravel/ai

The official AI SDK for Laravel.

1.1k6.4M363](/packages/laravel-ai)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

80732.6M276](/packages/laravel-mcp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
