codemonster-ru / security
Security components for Annabel ecosystem: CSRF protection and rate limiting.
Installs: 6
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/codemonster-ru/security
Requires
- php: >=8.2
- codemonster-ru/http: ^1.0
- codemonster-ru/session: ^1.0
Requires (Dev)
- codemonster-ru/annabel: ^1.0
- phpunit/phpunit: ^9.6 || ^10.5 || ^11.0 || ^12.0
Suggests
- codemonster-ru/annabel: Registers SecurityServiceProvider to enable middleware integration
This package is auto-updated.
Last update: 2025-12-28 14:14:15 UTC
README
codemonster-ru/security is a set of reusable security components for the Annabel ecosystem:
- CSRF protection (
VerifyCsrfToken) with a token from POST (_token) and/or headers (X-CSRF-TOKEN,X-XSRF-TOKEN) - Rate limiting / brute-force protection (
ThrottleRequests) with a configurable key and storage layer
No Laravel/Symfony dependencies. Compatible with codemonster-ru/http and codemonster-ru/session.
Installation
composer require codemonster-ru/security
For monorepo development, you can use a path repository (as in annabel-skeleton/composer.local.json).
Quick Start (Annabel)
Annabel loads providers from bootstrap/providers/*.php.
- Add a provider:
bootstrap/providers/SecurityServiceProvider.php
<?php namespace App\Providers; use Codemonster\Security\Providers\SecurityServiceProvider as BaseSecurityServiceProvider; class SecurityServiceProvider extends BaseSecurityServiceProvider {}
- Add config:
config/security.php
<?php return [ 'csrf' => [ 'enabled' => true, 'add_to_kernel' => true, 'verify_json' => false, 'input_key' => '_token', 'except_methods' => ['GET', 'HEAD', 'OPTIONS'], 'except' => ['api/*'], ], 'throttle' => [ 'enabled' => true, 'add_to_kernel' => false, 'max_attempts' => 60, 'decay_seconds' => 60, 'storage' => 'session', // session | database | redis 'connection' => null, // database connection name 'table' => 'throttle_requests', 'redis' => null, // Redis client instance or container id/class 'prefix' => 'throttle:', 'presets' => [ 'login' => [ 'ip' => ['max_attempts' => 60, 'decay_seconds' => 60], 'account' => [ 'max_attempts' => 5, 'decay_seconds' => 60, 'field' => 'email', ], ], 'api' => ['max_attempts' => 120, 'decay_seconds' => 60], ], 'except' => [], 'trusted_proxies' => ['10.0.0.0/8'], ], ];
By default, CSRF is enabled globally (via Kernel::addMiddleware), but throttling is not (so as not to surprise all routes).
CSRF
How is it checked?
Codemonster\Security\Csrf\VerifyCsrfToken:
- Skips methods from
except_methods(GET/HEAD/OPTIONSby default) - By default, does not validate JSON requests (if
Accept: application/json) to avoid breaking the API - Validates the token:
- In the body:
_token(configured viainput_key) - Or in the headers:
X-CSRF-TOKEN,X-XSRF-TOKEN
- In the body:
- On error, returns
419(application/jsonortext/plain)
Security note: if your API uses cookies or other stateful auth, enable verify_json to protect JSON POST/PUT/PATCH/DELETE requests too.
Helpers
The package autoloads helpers:
csrf_token(): stringcsrf_field(): string- ready-to-use<input type="hidden" name="_token" ...>
Example in the form:
echo '<form method="POST" action="/submit">'; echo csrf_field(); echo '<button type="submit">OK</button>'; echo '</form>';
Throttle / Rate limiting
Codemonster\Security\RateLimiting\ThrottleRequests:
- stores the attempt counter in storage via
ThrottleStorageInterface - the package contains at least one implementation:
SessionThrottleStorage(without a database) - for shared storages, prefer atomic increments (implement
AtomicThrottleStorageInterface) to avoid race conditions - returns
429+ headers:Retry-After(seconds)X-RateLimit-LimitX-RateLimit-RemainingRateLimit-LimitRateLimit-RemainingRateLimit-Reset(unix timestamp)
Best practices
- Enable
verify_jsonfor stateful APIs (cookies, sessions) to avoid CSRF bypasses. - Configure
trusted_proxieswhen running behind a proxy; otherwiseX-Forwarded-Forshould be ignored. - Use database or Redis storage in multi-node deployments to avoid per-node limits.
Trusted proxies
If your app sits behind a reverse proxy or load balancer, configure trusted_proxies so the middleware can safely use X-Forwarded-For or X-Real-IP.
When trusted_proxies is empty, only REMOTE_ADDR is trusted. Do not trust these headers unless the proxy is under your control.
Database storage (MySQL)
If you want atomic throttling across multiple nodes, use the database storage:
'throttle' => [ 'storage' => 'database', 'connection' => null, 'table' => 'throttle_requests', ],
Register the package migrations path (Annabel):
// config/database.php return [ 'migrations' => [ 'paths' => [ base_path('database/migrations'), base_path('vendor/codemonster-ru/security/migrations'), ], ], ];
Without Annabel/Database:
- Copy migrations from
vendor/codemonster-ru/security/migrationsinto your project migrations directory. - Run your migrations as usual.
Custom table name example:
'throttle' => [ 'storage' => 'database', 'table' => 'app_rate_limits', ],
Note: the bundled migration reads security.throttle.table to decide which table to create.
If you don't use migrations, create the table manually (adjust name if needed):
CREATE TABLE `throttle_requests` ( `key` VARCHAR(191) NOT NULL, `attempts` INT NOT NULL DEFAULT 0, `expires_at` INT NOT NULL DEFAULT 0, PRIMARY KEY (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Redis storage
Provide a Redis client and set storage to redis:
'throttle' => [ 'storage' => 'redis', 'redis' => Redis::class, // container id/class or instance 'prefix' => 'throttle:', ],
Connection to the route
Router/Kernel in Annabel supports route-level middleware:
use Codemonster\Security\RateLimiting\ThrottleRequests; $app->post('/login', fn($req) => 'ok') ->middleware(ThrottleRequests::class, '5,60'); // 5 attempts in 60 seconds
Preset example:
$app->post('/login', fn($req) => 'ok') ->middleware(ThrottleRequests::class, 'login');
Restriction key
By default, the key is built from ip|method|path and hashed (sha1).
You can pass a callable instead of a role string:
use Codemonster\Security\RateLimiting\ThrottleRequests; $app->post('/login', fn($req) => 'ok') ->middleware(ThrottleRequests::class, function ($req) { return 'login:' . ($req->input('email') ?? 'guest') . '|' . $req->ip(); });
Tests
composer test
Optional E2E env (tests are skipped if not set):
- MySQL:
MYSQL_HOST,MYSQL_PORT,MYSQL_DATABASE,MYSQL_USERNAME,MYSQL_PASSWORD - Redis:
REDIS_HOST,REDIS_PORT,REDIS_PASSWORD,REDIS_DB