iseazy/security
A Laravel security package from Iseazy offering reusable helpers and hardening utilities for PHP applications, helping teams centralize common protections and keep authentication and authorization behavior consistent across projects.
The package is iseazy/security, published from isEazy-Engage/iseazy-security-bundle. The repository name ends in -bundle, which is the strongest signal available: this is a Symfony bundle, not a framework-agnostic library and not a Laravel package. Treat every step below as "verify, then wire", because with 0 stars and 0 dependents there is no ecosystem of blog posts or Stack Overflow answers to fall back on.
1. Confirm the shape of the package before designing anything around it.
composer require iseazy/security
cat vendor/iseazy/security/composer.json # PHP + symfony/* constraints, autoload prefix, extra.symfony
ls vendor/iseazy/security/src
Read composer.json first. It tells you three things you cannot guess:
use statements,extra.symfony / Symfony\Bundle\FrameworkBundle flex recipe or expects manual registration.2. Register the bundle. Find the class ending in Bundle and add it to config/bundles.php:
// config/bundles.php
return [
// ...
Iseazy\Security\IseazySecurityBundle::class => ['all' => true],
];
If the namespace differs from what you assumed, that is your first integration bug — the bundle class is the single source of truth for the namespace, not the Composer package name.
3. Find the extension alias, then write the config file. The alias is whatever getAlias() returns (or the root node name in src/DependencyInjection/Configuration.php). Confirm it rather than guessing:
bin/console debug:config iseazy_security
bin/console config:dump-reference iseazy_security
config:dump-reference is the fastest way to learn the entire public surface of an undocumented bundle — it prints the config tree with defaults and comments.
4. Where to look first, in priority order.
| Location | What it tells you |
|---|---|
README.md |
May be sparse or internal-only; check last-updated date |
src/DependencyInjection/Configuration.php |
The real API: every option and default |
src/DependencyInjection/*Extension.php |
Which services are conditionally registered and under what config |
src/Resources/config/services*.yaml |
Service IDs, tags (security.authenticator, security.voter), and aliases you can override |
src/Security/ |
Authenticators, user providers, voters, password hashers — the actual behaviour |
src/Attribute/ or src/Annotation/ |
Whether it offers PHP-attribute-based authorization |
5. First use case to attempt. Do not start by rewriting your firewall. Pick the smallest observable behaviour:
401/302 in a functional test.403 for an unauthorized user.// minimal first integration: one action, one assertion
#[Route('/probe', name: 'probe')]
public function probe(): JsonResponse
{
$this->denyAccessUnlessGranted('ISEAZY_PROBE');
return $this->json(['ok' => true]);
}
Then bin/console lint:container and bin/console debug:container --tag=security.voter to confirm the bundle actually registered what you think it did.
Config-file-per-environment. Put the shared block in config/packages/iseazy_security.yaml and only the differing keys (keys/secrets/audience/endpoints) in config/packages/{dev,prod}/iseazy_security.yaml. Because the config tree is the bundle's primary API, keeping your usage declarative means an upgrade diff in Configuration.php surfaces immediately as a container lint error rather than a runtime 500.
Firewall integration, not firewall replacement. A bundle like this almost never wants to own your whole security.yaml. Slot its authenticator into the existing firewall and let your app's user provider remain authoritative:
# config/packages/security.yaml
security:
firewalls:
api:
pattern: ^/api
stateless: true
custom_authenticators:
- Iseazy\Security\Security\SomeAuthenticator # verify the real FQCN
entry_point: Iseazy\Security\Security\SomeAuthenticator
Two details bite here: the authenticator must appear before the blanket main firewall in ordering terms (firewalls are first-match), and entry_point must be set explicitly or an unauthenticated request falls through to Symfony's default behaviour instead of the bundle's challenge response.
Wrap it behind your own interface. With zero dependents there is no community pressure keeping the public classes stable. Introduce a thin boundary in your app:
interface TokenVerifierInterface { public function verify(string $token): VerifiedIdentity; }
Bind the bundle's service to that interface in services.yaml, and inject the interface everywhere. A breaking change in the bundle then costs you one adapter file, not a codebase-wide rename. This is the single highest-value pattern for a low-adoption dependency.
Decorate for behaviour you need but the bundle lacks. If you need audit logging, clock skew tolerance, or claim normalization, decorate rather than fork:
services:
App\Security\AuditingVerifier:
decorates: iseazy.security.some_verifier # confirm the service id
arguments: ['@.inner']
Decorate only services the bundle marks public or that are referenced via alias — internal/private services in Resources/config/ are fair game for decoration but not for direct get() from the container.
Override a service by alias, not by editing vendor. Many Symfony bundles define SomeInterface: '@some.concrete.service'. Redefining that alias in your services.yaml swaps the implementation with zero code changes. Check debug:container SomeInterface to see whether the alias exists — if it does, this is your cleanest extension point.
Testing pattern. Because the package's own test suite tells you nothing about your integration, write the contract test yourself:
public function test_unauthenticated_request_is_rejected(): void
{
$this->client->request('GET', '/api/probe');
self::assertResponseStatusCodeSame(401);
}
Run this in CI. When you bump the bundle version, this test — not the changelog — tells you whether behaviour changed.
Pin and diff on upgrade. Given the release cadence, use composer require iseazy/security:^X.Y with a caret on a version you have actually tested, and read git diff vPREV..vNEXT -- src/ before bumping. Two minutes of source diff beats an afternoon of debugging.
The extension-alias mismatch is the #1 first-run failure. "There is no extension able to load the configuration for iseazy_security" means your YAML filename/root key does not match getAlias(). Run bin/console config:dump-reference with no arguments to list every registered alias, and use exactly that.
Package name ≠ namespace ≠ config alias. iseazy/security may map to Iseazy\Security\..., and the config alias may be iseazy_security — or something else entirely. Derive all three from the bundle source, never from the Composer name.
Autowiring is not guaranteed. Internal bundles frequently ship explicit Resources/config/services.yaml definitions with hand-written arguments, and they may not expose interfaces suitable for autowiring. If you get "Cannot autowire service ... argument $x of type string", that service is not autowirable — inject it by its service ID or check whether an interface alias exists. Don't public: true a vendor service to work around this; decorate it.
Stale container after config changes. A new config key that "does nothing" is nearly always a cached container. bin/console cache:clear in the environment that is actually serving the request, not just your shell.
Firewall ordering and entry_point. Symfony evaluates firewalls top-down, first match wins. Adding the bundle's authenticator to a firewall defined below a broader pattern means it never runs. Explicitly set entry_point on stateless API firewalls or unauthenticated requests will not produce the challenge response you expect.
Event-subscriber priority collisions. If the bundle listens on security.authentication.success, security.interactive_login, or lexik_jwt_authentication.on_jwt_created, a subscriber of yours at the same priority can silently win or lose depending on registration order. Inspect with:
bin/console debug:event-dispatcher security.interactive_login
Symfony version constraints are a hard wall. Read composer.json's symfony/* requirements before planning a framework upgrade. A 0-star bundle is not going to be patched promptly for a new major. If you cannot upgrade Symfony without dropping the bundle, know that before the upgrade sprint, not during it.
Debugging checklist, in order:
bin/console lint:container # wiring errors
bin/console debug:config iseazy_security # what config was actually resolved
bin/console debug:container --tag=security.authenticator
bin/console debug:container --tag=security.voter
bin/console debug:event-dispatcher | grep -i security
tail -f var/log/dev.log # channel: security / request
Set monolog.channels: ["security"] to debug when you need the authentication decision trail — Symfony's security logging is verbose and precise, and it is the fastest way to see which listener denied the request.
Prefer attributes over security.yaml for authorization rules if the bundle supports them. Authorization expressed next to the controller action survives firewall refactors; rules buried in YAML do not.
No dependents means no safety net. Assume: the README may be internal-facing, semver may be aspirational, and a patch release may change behaviour. Mitigate with the adapter interface, the pinning strategy, and a small but real integration test suite. Those three things are cheap now and expensive to retrofit.
Extension points ranked by safety: (1) config keys, (2) service alias override, (3) decorator, (4) compiler pass, (5) forking into your own namespace. Stop at the lowest level that solves the problem — with an unmaintained-by-others bundle, escalating to (5) is a signal to reconsider the dependency entirely.
How can I help you explore Laravel packages today?