What is Magento ACL ?
ACL (Access Control List) in Magento 2 is the role-based-permission system that gates admin user access to admin features, system configuration sections, and REST/SOAP API endpoints. Each module declares its permission resources in etc/acl.xml as a nested tree under the root Magento_Backend::admin resource; admin controllers check them via _isAllowed() and the ADMIN_RESOURCE constant. Admin roles are named sets of granted resources, and users plus integrations both check ACL via Magento\Framework\AuthorizationInterface.
Five layers from acl.xml declaration to runtime permission check
Magento’s ACL spans declarative XML, controller-level constants, config-section attributes, REST/SOAP route bindings, and an admin role-edit UI. Here is the wiring, end to end.
-
01
Module declares ACL resources in etc/acl.xml
Every module that needs admin-side permission gating ships an
etc/acl.xmlfile with a nested resource tree:<config><acl><resources><resource id="Magento_Backend::admin"><resource id="Vendor_Module::manage" title="Vendor Module" sortOrder="10"><resource id="Vendor_Module::edit" title="Edit"/></resource></resource></resources></acl></config>. The dotted-keyid(e.g.Vendor_Module::manage) becomes the permission identifier used everywhere else; thetitleattribute is the human-readable label rendered in the admin role-edit checkbox tree. -
02
Admin controller checks the resource via _isAllowed()
A custom admin controller — e.g.
Vendor\Module\Controller\Adminhtml\Manage\Index— extends\Magento\Backend\App\Actionand overridesconst ADMIN_RESOURCE = 'Vendor_Module::manage';. Magento’s BackendAction base class calls_isAllowed()on every request, which checks the logged-in admin user’s role against the declared ACL resource. If the role does not have the resource ticked, Magento returns a403denied response and the controller body never executes. No_isAllowed()override + noADMIN_RESOURCEmeans the controller is effectively wide-open to any logged-in admin. -
03
system.xml ties config sections to ACL resources
Admin configuration sections defined in
etc/adminhtml/system.xmldeclare aresource="..."attribute:<section id="vendor_module" resource="Vendor_Module::manage_config">...</section>. That means the admin user can only see and edit thatStores → Configurationsection if their role has the matching ACL resource ticked. The same pattern works at the group level for finer-grained gating — an accounting role might see the section but only the “tax rates” group, not the “API credentials” group. -
04
webapi.xml ties REST/SOAP endpoints to ACL resources
Every REST and SOAP endpoint declared in
etc/webapi.xmlbinds to an ACL resource:<route url="/V1/vendor-module/items" method="GET"><resources><resource ref="Vendor_Module::view"/></resources></route>. The OAuth integration calling that endpoint needs the matching ACL resource granted underSystem → Extensions → Integrationsat activation time. This is the same authorization surface as the admin UI — one acl.xml tree gates both human admins and third-party integrations. -
05
Admin role config renders the acl.xml tree as a checkbox forest
System → Permissions → User Roles → Edit Role → Role Resourcesrenders the merged acl.xml tree (every module’s resources combined) as a nested checkbox list. The admin ticks the resources granted to that role — e.g. orders + invoices but NOT customers or catalog. Users are then assigned to roles underSystem → Permissions → All Users. Integrations have a parallel ACL config underSystem → Extensions → Integrations, separate from human-user roles, so a marketplace-sync OAuth token can be locked to a different resource set than any human admin.
Four scenarios where ACL discipline is non-negotiable
ACL is not optional plumbing on a serious Magento store. These four scenarios are where missing or sloppy ACL discipline turns into compliance findings, integration risk, or outright privilege escalation.
-
Building a custom admin module
Every controller, every config section, every REST endpoint in a custom admin module needs ACL gating — without it, anyone with admin login sees everything regardless of role. The minimum surface: one
etc/acl.xmltree, oneADMIN_RESOURCEconstant per admin controller, aresource="..."attribute on everysystem.xmlsection, and a<resource ref="..."/>on everywebapi.xmlroute. Skip any one and you have leaked a privilege escalation surface that role-based-access reviewers will flag. -
Granular delegation across multi-team admin estates
Larger merchants run separate admin teams — accounting needs orders + invoices but NOT customer PII or catalog edits; marketing needs CMS + email but NOT orders or refunds; warehouse needs shipments + stock but NOT pricing. Roles in
System → Permissions → User Roleslet you tick exactly the right resources per team. If your store has more than three admin users with different job functions, you should already be on at least three distinct ACL roles, not one sharedAdministratorsrole. -
Third-party API integrations (ERP, CRM, marketplace)
Every third-party integration that talks to Magento — ERP push, CRM sync, marketplace connector, shipping-label printer, marketing-automation tool — gets its own integration credentials under
System → Extensions → Integrations. The ACL tab on that integration form is where you lock the OAuth token to the exact resources the integration needs. A marketplace-sync token should never getMagento_Backend::adminroot; it getsMagento_Catalog::products+Magento_Sales::sales_orderand nothing else. -
Compliance audits (PCI, SOC 2, ISO 27001)
Every formal compliance audit — PCI DSS, SOC 2 Type II, ISO 27001, HIPAA where applicable — checks that admin users operate under least-privilege access. ACL is the implementation surface the auditor will inspect: role definitions, per-role resource lists, named user-to-role mappings, and an evidence trail showing that resource grants match documented job functions. A clean ACL story (10+ named roles, no shared
Administratorsaccount, no integration on root) makes the audit straightforward; a one-role-fits-all setup invites findings.
Three ACL mistakes that leak admin privileges
Every ACL-related Magento engagement I’ve been called in to audit had at least one of these three mistakes. Fix them before the next compliance review or pen-test.
-
Forgetting ADMIN_RESOURCE in a custom admin controller
Without
const ADMIN_RESOURCE = 'Vendor_Module::resource';on a controller extending\Magento\Backend\App\Action, Magento falls back to a permissive default and every admin user with login access can hit the controller regardless of role. This is the single most common privilege-escalation bug in custom admin modules. Always setADMIN_RESOURCEto the most specific resource the action should require —Vendor_Module::editfor an edit action,Vendor_Module::deletefor a delete, and so on. Override_isAllowed()only when you need conditional logic beyond a simple resource check. -
Granting roles Magento_Backend::admin (the root resource)
Ticking the root
Magento_Backend::admincheckbox in a role-edit screen opens every child resource in the tree — the entire admin estate. Only true super-admin roles (typically one or two named individuals, plus break-glass) should ever have the root checked. Everyone else gets specific child resources matching their job function. The temptation to tick the root for a “senior” role to skip the granular work is exactly what compliance auditors flag and what attackers exploit on a compromised admin account. -
Adding ACL resources that don't appear in the role-edit tree
Magento renders only resources actually declared in a merged
etc/acl.xmlfile. If your new resource never shows up inSystem → Permissions → User Roles → Role Resources, the file is missing, the resourceidis misspelt, the parent path doesn’t exist, or you forgot tobin/magento cache:flushafter deploying. You cannot grant a resource that isn’t in the tree — controllers referencing such a resource viaADMIN_RESOURCEwill always 403. Validate by searching the merged ACL XML at runtime or by clearing the config cache and reloading the role-edit page.
Magento ACL — frequently asked questions
-
ACL vs admin role vs user — what’s the difference?
Three distinct layers. ACL is the resource tree itself — the merged set of <code>etc/acl.xml</code> declarations from every installed module, rendered as a nested hierarchy under <code>Magento_Backend::admin</code>. Roles are named sets of granted resources — an <code>Accounting</code> role might be ticked for orders + invoices but nothing else. Users are individual admin accounts assigned to one or more roles — <code>jane@example.com</code> gets the <code>Accounting</code> role, <code>bob@example.com</code> gets <code>Marketing</code>. Integrations are a parallel surface: an OAuth integration token has its own resource list, separate from any human-user role, and the resources granted there gate REST/SOAP API access via <code>etc/webapi.xml</code>. -
Why doesn’t my custom admin page enforce permissions?
Three likely causes. First: the controller is missing the <code>const ADMIN_RESOURCE = 'Vendor_Module::resource';</code> declaration, so Magento’s default <code>_isAllowed()</code> returns true for everyone. Always set <code>ADMIN_RESOURCE</code> on every admin controller. Second: the <code>etc/acl.xml</code> file is missing, malformed, or the resource <code>id</code> doesn’t exactly match what the controller declares — case-sensitive, including the <code>Vendor_Module::</code> prefix. Third: stale cache — ACL is config-cache-backed, so deploy + <code>bin/magento cache:flush</code> is required after any acl.xml change before the role-edit tree picks up the new resource. -
How do I restrict an admin user to a single store-view?
Use Role Scopes, not ACL. Under <code>System → Permissions → User Roles → Edit Role → Role Resources</code> there is a <code>Role Scopes</code> tab that lets you tick which websites or store-views the role applies to. This is store-scope restriction — orthogonal to the resource tree. Role Scopes complements ACL but does not replace it; you still need to tick the right resources on the <code>Role Resources</code> tab to gate what the role can do, and use Role Scopes to gate which scope it applies that to. A typical multi-brand merchant runs one <code>Brand X Manager</code> role per brand, each with the same resource set but a different Role Scope. -
Are REST API integrations bound to ACL?
Yes, fully. Under <code>System → Extensions → Integrations → Add New Integration</code> you create an integration, give it a name and contact email, then on the <code>API</code> tab you tick the ACL resources that integration’s OAuth token is allowed to hit. The user activates the integration once, Magento mints the access token, and every REST/SOAP call carrying that token is checked against the granted resources. The resources declared on <code>etc/webapi.xml</code> routes (<code><resource ref="..."/></code>) must match a resource the integration was granted, or the API call returns 401/403. The standard rule: one integration per third-party tool, scoped to exactly the resources it needs, never to <code>Magento_Backend::admin</code>. -
Can a role have access to admin but not to all stores?
Yes — that’s exactly what Role Scopes is for. Role Scopes restricts a role to specific websites or store-views; it’s separate from the resource-tree ACL. A typical multi-store merchant runs one <code>Region Manager — EU</code> role with full catalog + orders resources scoped to the EU website only, and a separate <code>Region Manager — US</code> role with the same resources scoped to the US website. The two role-holders never see each other’s store data even though their ACL resource lists are identical. The combination of Role Resources (what they can do) plus Role Scopes (where they can do it) is how Magento expresses multi-tenant admin segregation. -
What’s the difference between Magento’s ACL and Laminas Acl?
Magento uses Laminas Acl under the hood — <code>Magento\Framework\Acl</code> extends <code>Laminas\Permissions\Acl\Acl</code> — so the runtime evaluation logic (resource hierarchy, role inheritance, allow/deny rules) is the Laminas implementation. What Magento adds on top is the declarative XML wrapper: the merged <code>etc/acl.xml</code> tree across every installed module becomes the resource graph, role definitions live in the database (<code>authorization_role</code> + <code>authorization_rule</code> tables) and are managed through the admin UI rather than PHP code, and the <code>AuthorizationInterface</code> service binds it all to the logged-in admin user’s session. You almost never call Laminas Acl directly — the abstraction is <code>\Magento\Framework\AuthorizationInterface::isAllowed('Vendor_Module::resource')</code>.
Want an ACL audit on your Magento admin?
Send your storefront URL — I will review your acl.xml declarations, role definitions, named-user-to-role mappings, integration scopes, and any custom admin controllers, then reply with a written tuning plan, fixed-price quote, and earliest start date. 24-business-hour turnaround.