Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Security Bundle Laravel Package

ns/security-bundle

A PHP security bundle for Laravel applications that centralizes common security configuration and protections. It helps teams add consistent safeguards, reduce boilerplate, and keep security settings easier to manage across projects, with a focus on simple setup and maintainability.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Confirm the package identity first. The repo is NobletSolutions/SecurityBundle but the package name is listed as ns/security-bundle. Check composer.json inside the repo for the real name field before running composer require; a mismatch here is the most common reason the install "fails" for a 0-star package. With no tagged releases, expect to pin a commit:

    composer require ns/security-bundle:dev-master#
    
  • Where to look in the source, in this order:

    1. DependencyInjection/Configuration.php and *Extension.php — tells you the config keys (table/entity name, connection, maybe cache).
    2. Entity/ or Model/ — the ACL row shape. You need to know whether it stores (objectType, objectId, identity, permission) as strings, ints, or a bitmask.
    3. The service that answers "can X do Y on Z?" (often an AclManager, SecurityManager, or checker service). Read this method line by line — it is the entire security boundary.
    4. Any Resources/config/doctrine/*.orm.xml or migrations — this is the schema you must install.
  • Register and configure. Small bundles often aren't in config/bundles.php automatically if they predate Flex recipes. Add new NobletSolutions\SecurityBundle\SecurityBundle() and a config/packages/security_bundle.yaml matching whatever keys step 1 revealed.

  • Install the schema into your migrations. Don't rely on the bundle to own the schema. Generate the table from the mapped entity, then copy the CREATE TABLE into an application migration with the indexes you want:

    CREATE TABLE acl_entry (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        object_type VARCHAR(255) NOT NULL,
        object_id   VARCHAR(64)  NOT NULL,
        identity    VARCHAR(255) NOT NULL,
        permission  SMALLINT     NOT NULL
    );
    CREATE INDEX acl_lookup ON acl_entry (object_type, object_id, identity);
    
  • First use case — owner access on create. The canonical row-level ACL workflow: after persisting an object, write an OWNER/ALL row for the creator, then read it back through the checker before rendering the object.

    // after $order is persisted
    $acl->grant('order', $order->getId(), $order->getOwner(), 'owner');
    
    // before rendering
    if (!$acl->isGranted('order', $order->getId(), $user, 'view')) {
        throw $this->createAccessDeniedException();
    }
    

    The exact method names and permission representation will not match the above — verify against the checker service before you build anything on top of it.

Implementation Patterns

  • Wrap the bundle in a Symfony voter. Do not call the bundle's checker directly from controllers or templates. Write one AclVoter per object type (or one generic voter keyed off the object class) that delegates to the bundle service and returns ACCESS_GRANTED/ACCESS_DENIED. This gives you isGranted('view', $order), #[IsGranted] attributes, Twig is_granted(), and a single place to add role/owner overrides later.

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        return $this->acl->isGranted(
            $this->typeOf($subject),        // stable object type, not get_class()
            $this->idOf($subject),
            $token->getUser(),
            $attribute
        );
    }
    
  • Store a stable object type, not a FQCN. Derive it from something you control (class constant, Order::ACL_TYPE) so moving namespaces doesn't invalidate every ACL row.

  • Auto-grant via Doctrine lifecycle listeners. A postPersist listener that writes the owner row keeps controllers free of ACL plumbing. Pair it with preRemove to delete rows so you don't accumulate orphans.

  • Batch the checks for listings. The naive pattern — one checker call per row inside a foreach over an index page — is an N+1 on the ACL table. Add a batch method (loadAcls(string $type, array $ids)) or at minimum memoize per request. For a 25-row page this is the difference between 1 and 25 queries.

  • Request-scoped cache, explicit invalidation. Cache [type, id, identity] => permissions in a per-request array or a short-TTL pool. Invalidate on grant()/revoke(); do not invalidate globally from an admin screen and hope.

  • Ownership as an implicit grant. If the bundle has no concept of "owner", encode it as a permission bit and have the voter treat owner as implying view/edit. Do not write five rows per object to express one idea.

  • Role and group inheritance, if absent, belongs in your voter. Check $token->getRoleNames() before falling through to the DB check. Keep the ordering explicit: owner → role grant → ACL row → deny.

  • Scope multi-tenant data in the key or the query. If two tenants can both have object id 17, either include a tenant column in the lookup or namespace the type (tenant_7_order). This is easy to miss until a cross-tenant leak appears.

  • Test at the voter level, seed at the repository level. Fixtures that insert ACL rows plus a handful of voter unit tests give you far more confidence than testing controllers, and they're fast.

Gotchas and Tips

  • Treat the code as unreviewed. Zero stars and zero dependents means no community scrutiny. Before shipping, read the access-check SQL/query builder for: missing object_id in the WHERE clause, string-interpolated values, and a default-allow branch when no rows are found. Deny-by-default is the only acceptable fallback.
  • Identity mismatch is the classic failure. If the bundle stores $user->getUsername() but you pass a UserInterface object (or vice versa), every check silently denies — or worse, allows. Confirm how identity is normalized and make your voter pass exactly that.
  • Object id type drift. object_id as VARCHAR(64) plus an integer id compares fine in most DBs but fails on UUIDs with trailing whitespace and on composite keys. Cast to (string) at the boundary and be deliberate.
  • get_class() vs proxies. Doctrine lazy proxies report generated class names. Never use get_class($entity) as the ACL type — use a constant or ClassUtils::getClass().
  • No cascade deletes. The bundle will not clean up ACL rows when the object or user is deleted. Add both listeners; otherwise your table becomes a graveyard and a slow leak of user ids in production.
  • Indexes are probably absent. Verify the schema has a composite index on the lookup columns. Without it the checker degrades to a table scan on every isGranted call.
  • Check the config surface before extending. If the table name is hardcoded, you either accept it or override the service/parameter. Read Configuration.php rather than guessing at YAML keys — an unknown key is silently ignored in many small extensions.
  • Pin and vendor. No tags means composer update can change your security boundary overnight. Pin the commit hash and consider reading the diff on every bump.
  • Debugging: dump the generated query from the Symfony profiler's Doctrine panel while exercising one page. If you see one ACL query per row, you have an N+1. A small console command that prints all ACL rows for a given type/id is worth the ten minutes.
  • Don't serialize the ACL entity. It exposes user identifiers and internal ids through any accidental json_encode of a relation.
  • Weigh the alternative honestly. A single indexed table plus a ~60-line voter covers the same ground as this bundle with code you can audit and own. Symfony's bundled ACL is abandoned; for row-level checks the custom-voter route is usually the lower-risk choice unless this bundle's already-implemented inheritance model saves you real work.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib