themusicdev / redis-acl
CakePHP 5 plugin: a RedisEngine subclass that authenticates as a Redis 6 ACL user (username + password), which the stock engine cannot
Package info
github.com/TheMusicDev/cakephp-redis-acl
Type:cakephp-plugin
pkg:composer/themusicdev/redis-acl
Requires
- php: >=8.2
- ext-redis: >=5.3
- cakephp/cakephp: ^5.2
Requires (Dev)
- cakephp/cakephp-codesniffer: ^5.3
- captainhook/captainhook: ^5.29
- captainhook/plugin-composer: ^5.3
- phpstan/phpstan: ^2
- phpunit/phpunit: ^11.5.3 || ^12.1.3 || ^13.0
- ramsey/conventional-commits: ^1.7
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-10 22:06:41 UTC
README
Auth to a Redis 6 ACL user from a CakePHP 5 cache config or redis:// URL. CakePHP's own RedisEngine has no
username key in its config, ignores the username in a DSN and calls Redis::auth() with a scalar password only — so a
Redis server running ACL users (username + password) cannot be used. This plugin ships one RedisEngine subclass and
one line of bootstrap; nothing else. It exists to be deleted the day CakePHP consumes the DSN username natively
(upstream issue).
Requires phpredis 5.3.0+ (ext-redis), where Redis::auth() first accepts the array form [username, password].
Install
composer require themusicdev/redis-acl
That is the whole setup. The package remaps the redis:// URL scheme from the stock engine to the ACL-capable one
through a Composer autoload.files entry, so DSNs and className configs both work with no application change.
Loading it as a plugin (config/plugins.php: 'TheMusicDev/RedisAcl' => []) is optional and does nothing extra.
The remap must happen at autoload time, not from a plugin bootstrap hook: Cache::setConfig() parses a url
eagerly and StaticConfigTrait::parseDsn() resolves className from the DSN class map at parse time, and a host
parses its Cache configs in config/bootstrap.php — which BaseApplication::bootstrap() requires before
pluginBootstrap() runs. Remapping from the plugin hook alone lands too late: the config already holds the stock
engine, which calls Redis::auth() with a scalar and fails at the first cache write, with nothing wrong at boot.
Use it
Either form reaches this plugin's engine:
// In config/app.php — a DSN with the ACL user 'sessions' => [ 'url' => 'redis://acluser:aclpass@redis.internal:6379?prefix=myapp_sessions_&database=1', 'duration' => 3600, ], // Or discrete keys 'sessions' => [ 'className' => \TheMusicDev\RedisAcl\Cache\Engine\AclRedisEngine::class, 'host' => 'redis.internal', 'username' => 'acluser', 'password' => 'aclpass', 'prefix' => 'myapp_sessions_', 'database' => 1, 'duration' => 3600, ],
Do not set a discrete className alongside a url: CakePHP's DSN parsing wins its merge ($parsed + $config), so
the className you set next to a url is silently discarded — the scheme remap is what makes the DSN form work.
URL-encode a password containing @, :, ? or #, or parseDsn() will split the DSN in the wrong place.
A username set without a password throws (CakeException) — it can only authenticate as nobody.
RedisCluster config is inherited from the stock engine unchanged and its array-auth path is not tested here; on a
cluster, verify against your phpredis version.
Sessions (the reason this plugin exists)
Sessions reach Redis through the cache config, so point Session.defaults => 'cache' at the config above:
'Session' => [ 'defaults' => 'cache', 'timeout' => 3600, // minutes 'cookie' => 'myapp', ],
Then Cake\Http\Session\CacheSession stores session data through the sessions (or renamed) cache config. Two things
to know — they are properties of cache-backed sessions, not of this plugin, and both bite in production:
- No session locking. PHP's native file handler locks the session file per request;
CacheSessionjust callsCache::write. Concurrent requests from one browser (parallel AJAX) can clobber each other's session writes — last write wins. - No
gc(). Session expiry is the Redis TTL, which comes from the cache config'sduration. Ifdurationis shorter than the sessiontimeout, Redis evicts live sessions: users get logged out early and intermittently. The rule:duration≥timeout. (timeoutis in minutes,durationin seconds: a two-hour session needstimeout => 120andduration => 7200or more.)
Shared Redis between apps
A per-app prefix, and ideally a distinct database=N, or two apps on one Redis will read each other's keys.
Tests
Unit tests need ext-redis on the runner (CI installs it via shivammathur/setup-php, extensions: redis). The
integration test — real auth to a real ACL user, with the default user disabled, so a scalar auth() cannot pass
by accident — runs only when REDIS_TEST_URL is set:
docker compose up -d --wait redistest
REDIS_TEST_URL='redis://acltest:aclpass@localhost:6381?prefix=int_' composer test
Delete this plugin later
When CakePHP ships ACL username support, remove the composer package, drop the plugin from config/plugins.php, and
remove username from your cache configs (or leave them — the upstream engine will ignore what it does not consume,
exactly as it does today).