Permissions Component
softspring/permissions-bundle is a small security component packaged as a Symfony bundle.
Its purpose is to make attributes that start with PERMISSION_ work like hierarchical security checks in Symfony.
That sounds small, but it creates a consistent authorization model across the Softspring ecosystem:
- application roles stay as
ROLE_* - action-level permissions use
PERMISSION_* - reusable packages can publish stable permission names in
role_hierarchy - applications can aggregate those permissions into business roles
- custom voters can still deny or refine access for concrete subjects
In practice, this is what makes checks such as is_granted('PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_UPDATE', $media) behave as expected.
Why Treat It As A Component
This package is better understood as a component than as an application-facing bundle because it does not provide:
- screens
- routes
- controllers
- persistence
- admin UI
- end-user workflows
It contributes one infrastructure capability to Symfony Security and is meant to be reused by higher-level bundles and applications.
Installation
composer require softspring/permissions-bundle:^6.0
If your application does not use Symfony Flex, enable it manually:
<?php
return [
// ...
Softspring\PermissionsBundle\SfsPermissionsBundle::class => ['all' => true],
];
What The Component Adds
The component registers one service:
- a
RoleHierarchyVoter - configured with the prefix
PERMISSION_ - under the service id
sfs_permissions.role_hierarchy.permission
That is the whole core.
It does not add:
- a permission database
- user-to-permission persistence
- ACL tables
- a UI to assign permissions
- a custom configuration tree
How It Works
Symfony already has a role hierarchy voter for ROLE_*.
This component registers a second hierarchy voter for the PERMISSION_ prefix.
That means a check like:
$this->denyAccessUnlessGranted('PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_UPDATE');
can be granted through role_hierarchy, even if the user does not literally have that string in the stored roles array.
Example:
security:
role_hierarchy:
ROLE_SFS_MEDIA_ADMIN_MEDIAS_RW:
- ROLE_SFS_MEDIA_ADMIN_MEDIAS_RO
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_CREATE
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_DELETE
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_UPDATE
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_MIGRATE
With the component enabled, a user with ROLE_SFS_MEDIA_ADMIN_MEDIAS_RW is also granted PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_UPDATE.
No Custom Configuration
There is no sfs_permissions: config block to define.
The component works through standard Symfony Security configuration:
security.role_hierarchyis_granted()denyAccessUnlessGranted()- custom voters
Why Use PERMISSION_* Instead Of ROLE_*
Using PERMISSION_* creates a clean separation between:
- business or user roles
ROLE_ADMINROLE_EDITORROLE_SFS_MEDIA_ADMIN_MEDIAS_RW
- atomic allowed actions
PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_LISTPERMISSION_SFS_MEDIA_ADMIN_MEDIAS_UPDATEPERMISSION_SFS_USER_ADMIN_USERS_PROMOTE
That separation gives practical benefits:
- reusable packages can ship stable permission names without deciding your final user roles
- applications can compose roles from permissions
- controllers and templates can check the exact action they need
- subject-specific voters can deny one permission without redefining the whole role model
Main Usage Pattern
The standard Softspring pattern has three layers:
- each package publishes atomic
PERMISSION_*names - each package groups them into convenience
ROLE_*roles when it makes sense - the application grants those roles to users or aggregates them into broader business roles
Package-Level Permission Groups
For example, media-bundle defines:
security:
role_hierarchy:
ROLE_SFS_MEDIA_ADMIN_MEDIAS_RO:
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_LIST
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_DETAILS
ROLE_SFS_MEDIA_ADMIN_MEDIAS_RW:
- ROLE_SFS_MEDIA_ADMIN_MEDIAS_RO
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_CREATE
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_DELETE
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_UPDATE
- PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_MIGRATE
Application-Level Aggregation
Projects such as armonic-standalone then aggregate package roles into final application roles:
security:
role_hierarchy:
ROLE_ADMIN:
- ROLE_SFS_USER_ADMIN_USERS_RW
- ROLE_SFS_MEDIA_ADMIN_MEDIAS_RW
- ROLE_SFS_CMS_ADMIN_BLOCKS_RW
- ROLE_SFS_CMS_ADMIN_CONTENTS_RW
This lets packages stay decoupled from your final business role model.
Real Usage Cases
Declarative CRUD Configuration
Reusable controller config often checks permissions directly:
list:
is_granted: 'PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_LIST'
create:
is_granted: 'PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_CREATE'
update:
is_granted: 'PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_UPDATE'
delete:
is_granted: 'PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_DELETE'
This is a good default pattern for reusable packages because it keeps permissions stable and lets each application decide how to grant them.
Direct Controller Checks
When one action does not fit a generic CRUD flow, check the permission directly:
$this->denyAccessUnlessGranted('PERMISSION_SFS_USER_ADMIN_USERS_PROMOTE', $user);
This keeps the controller explicit while still allowing hierarchy-based grants and subject-specific denials.
Twig Templates
Templates can use the same permission names:
{% if is_granted('PERMISSION_SFS_USER_ADMIN_USERS_UPDATE', user) %}
<a href="...">Update</a>
{% endif %}
The template does not need to know whether the grant came from:
- a direct role
- role hierarchy
- a custom voter
Declarative Menus
The same permissions work well in config-driven menus:
users:
route: 'sfs_user_admin_users_list'
role: PERMISSION_SFS_USER_ADMIN_USERS_LIST
This is useful when visibility should automatically follow authorization rules.
Combining Permissions With Custom Voters
This component grants permission attributes through hierarchy, but it does not stop you from adding more specific voters.
That is how several Softspring packages work.
For example, one voter can say the user is broadly allowed to recompile content, while another voter denies recompilation for one specific subject because it is disabled in the current state.
This gives you a layered model:
- role hierarchy says the user is broadly allowed
- a domain voter says this concrete subject is still not allowed right now
Why unanimous Works Well
Several Softspring applications use:
security:
access_decision_manager:
strategy: unanimous
That fits well with PERMISSION_* checks because:
- the hierarchy voter can grant the permission
- a subject-specific voter can deny it
- under
unanimous, the deny wins
Naming Convention
The common naming pattern is:
PERMISSION_<VENDOR OR APP>_<AREA>_<RESOURCE>_<ACTION>
Examples:
PERMISSION_SFS_MEDIA_ADMIN_MEDIAS_LISTPERMISSION_SFS_ACCOUNT_ADMIN_ACCOUNTS_UPDATEPERMISSION_SFS_USER_ADMIN_USERS_PROMOTEPERMISSION_SFS_CMS_ADMIN_SECTION_VERSION_DELETE
This matters because permission names become part of the reusable API exposed by each package.
Limitations
These limits are deliberate:
- there is no persistence layer
- there is no admin UI
- there is no object-level logic by itself
- only the
PERMISSION_prefix is handled
If you need dynamic permission assignment or object-specific rules, build that in your application and combine it with Symfony voters.