expertcoder/symapi-security-bundle
Symfony bundle for API security. Integrates security configuration into Symfony API applications, giving developers a packaged way to manage authentication and access concerns. Composer-installable and simple to register in a Symfony project.
Start with the framework check, because it determines everything else. expertcoder/symapi-security-bundle is a Symfony bundle (*Bundle, DependencyInjection/, Kernel integration), not a Laravel package. There is no ServiceProvider, no package discovery, no register()/boot(). It will not install into a Laravel composer.json in any usable way — Composer may resolve it, but nothing will boot it. Confirm this yourself in ten minutes before planning anything:
composer.json — check the symfony/* constraints. A cap at Symfony 4/5 tells you how old the code is and whether it would even boot on a supported framework.src/ end to end. Stars = 0, dependents = 0, archived = true. There is no README-driven tutorial, no Stack Overflow answer, no blog post. The source is the documentation, and you are the reviewer.DependencyInjection/ extension class first. The config tree keys there tell you exactly what the bundle wires up — that is faster than reading the security wiring cold.The realistic "getting started" for a Laravel developer is a reading exercise, not an install. Timebox it to a day and produce a one-page behavior spec: what authenticates, what it reads from the request (header name, scheme, token format), what it resolves a token to, what it returns on failure (JSON 401 vs. redirect to a login form — the classic Symfony firewall default), and what access-control rules it applies.
First use case to prove out — in your own code, not theirs: one route group, one token-in-header check, one happy path, one failure path. In Laravel that is a small middleware plus a guard, and it takes an afternoon:
// app/Http/Middleware/ApiTokenAuth.php
public function handle(Request $request, Closure $next)
{
$token = $request->bearerToken() ?? $request->header('X-Api-Key');
$client = ApiClient::where('token_hash', hash('sha256', (string) $token))->first();
if (! $client || $client->revoked_at) {
return response()->json(['error' => 'unauthenticated'], 401);
}
$request->attributes->set('api_client', $client);
return $next($request);
}
Wire it on the API group, not globally, and register it after TrimStrings/SubstituteBindings ordering decisions are made deliberately.
Where to look first if you genuinely maintain a Symfony app using this: the security factory/authenticator class and the example security.yaml. The firewall block (provider, stateless: true, guard/custom_authenticator, entry_point) is where the behavior lives, and firewall ordering in security.yaml is where the bugs live.
If you must keep the code around: fork it into your own VCS namespace immediately and point Composer at the fork via a vcs repository entry. You own it now; upstream is read-only.
The bundle's stated value is centralized API security rules shared across multiple entry points. That intent is worth keeping even if the code is not. Three patterns translate directly.
1. Rules as a config table, not as scattered conditionals. The bundle models firewalls + access control as declarative config. The Laravel equivalent is keeping the rules in config/ and having a single place that reads them, rather than sprinkling if ($user->role === ...) through controllers.
// config/api_security.php
return [
'guards' => [
'public' => ['routes' => ['api/public/*'], 'auth' => false],
'client' => ['routes' => ['api/v1/*'], 'auth' => true, 'abilities' => ['read']],
'partner' => ['routes' => ['api/partner/*'],'auth' => true, 'abilities' => ['read', 'write']],
],
];
Then one middleware consults the table. The win is that a security review reads one file, not forty controllers — which is exactly the property the bundle was selling.
2. Multiple entry points, one authenticator. The bundle advertises "centralized rules shared across multiple API entry points." In Laravel that is a single guard + middleware applied to several route groups (routes/api.php, a versioned v2 group, an admin API group), so token parsing and failure behavior can never drift between them. If you find yourself writing a second token-parsing branch for a new entry point, stop — parameterize the first one.
3. Spec-then-reimplement workflow. Treat the archived source as prior art:
Auth guards → authentication, route middleware → enforcement, Gates/Policies → authorization, Sanctum/Passport → token issuance and validation.Integration tip — enforce before the controller, always. Whatever mechanism you use, authorization should not depend on the controller remembering to call it. Middleware or route-group binding, plus a policy check for object-level access, is the shape you want. The bundle's firewall/access-control split maps onto exactly that: authenticate at the boundary, authorize per resource.
Integration tip: token lookup should be constant-ish time and revoked-aware. If the bundle's approach (or your port) does a linear scan or a plaintext comparison, hash the stored token and use an indexed lookup plus hash_equals. Cheap, and it removes a whole class of review findings.
Framework mismatch is the headline gotcha, not a footnote. No amount of configuration makes a Symfony bundle boot in Laravel. Symfony bundles depend on the Kernel, container compilation passes, the extension config tree, and the Symfony EventDispatcher. Laravel has its own container semantics, its own middleware pipeline, and package discovery. There is no bridge. Any plan that assumes composer require followed by a config publish is wrong; treat the package as reference material.
Archived security code decays into a liability, and it fails quietly. The dangerous failure mode is not an exception — it is the bundle silently ceasing to enforce because the runtime moved underneath it (PHP version bump, framework upgrade, a dependency resolving to a different major). "Fails open" on an auth path is the reason archival status alone is disqualifying here, independent of the framework question.
Failure response shape bites integrations. Symfony firewalls default to redirecting unauthenticated users to a login form unless the firewall is stateless with an explicit entry_point. An API client receiving a 302 to /login instead of a 401 JSON body is a classic symptom. When you port the behavior, pin the contract: 401 with a stable body for unauthenticated, 403 for authenticated-but-forbidden, and document it.
Zero tests means you are the test suite. With no dependents, there is no community memory of edge cases — header casing, whitespace in bearer tokens, empty tokens, multiple Authorization headers, token present but expired. Enumerate these yourself and write a test per line; they are cheap and they are the whole value of the exercise.
Composer lock drift. Abandoned packages frequently pin transitive dependencies and then block upgrades across the graph. If it is already in a legacy Symfony service, expect the removal ticket to be entangled with a dependency upgrade — sequence accordingly.
Symfony-specific, if you are actually on Symfony: firewall ordering matters (first match wins), stateless: true is required for APIs or you will get session behaviour you did not ask for, and access_control rules are evaluated in order rather than by specificity. Verify by hitting a route that should be public and one that should be protected, not by reading the YAML.
Extension points to look for during the audit. The DI extension class (config keys), the security factory/authenticator (the actual auth decision), and any compiler pass (where services get decorated or replaced). If the authenticator is a single small class with no branching, the port is trivial and you should just do it. If it is spread across listeners and a custom event subscriber, budget properly — that is where the audit time goes.
Confirm licensing before copying anything. Read the LICENSE file before vendoring code or lifting logic. "Archived" says nothing about the license terms, and an unattributed copy is a legal problem independent of the technical one.
Bottom line for the day-to-day question: you do not leverage this package in a Laravel codebase, and the honest answer for a Symfony codebase is that you migrate off it. Use it for the design vocabulary — firewalls, access rules, stateless token auth, one authenticator across many entry points — and implement those ideas against Laravel's native stack (Sanctum/Passport, guards, middleware, Gates/Policies) or a managed IdP. That gives you the same structure with a security patch cadence attached.
How can I help you explore Laravel packages today?