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.
composer require codemonster-ru/securityFor monorepo development, you can use a path repository (as in annabel-skeleton/composer.local.json).
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).
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.
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>';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)
- 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.
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.
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;Provide a Redis client and set storage to redis:
'throttle' => [
'storage' => 'redis',
'redis' => Redis::class, // container id/class or instance
'prefix' => 'throttle:',
],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 secondsPreset example:
$app->post('/login', fn($req) => 'ok')
->middleware(ThrottleRequests::class, 'login');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();
});composer testOptional 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