ACL and Permissions for Custom GraphQL Endpoints
ACL and Permissions for Custom GraphQL Endpoints
~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
Chapter 1 already touched on this: GraphQL doesn't automatically inherit the ACL model from webapi.xml. The access checks so far (chapters 17-18) only concerned customers - guest vs. logged in. This final chapter of block 6 shows the rarer, but practically relevant, case: a GraphQL endpoint that should only be accessible to admin users holding a specific ACL permission.
An admin-only reporting field
A realistic use case: an internal report showing how often each event has been favorited - useful for a back-office dashboard, but not something every anonymous storefront client should see.
type Query {
eventFavoritesReport: [EventFavoritesReportItem]
@resolver(class: "Mironsoft\\Event\\Model\\Resolver\\EventFavoritesReport")
@doc(description: "Admin-only report of favorite counts per event")
}
type EventFavoritesReportItem @doc(description: "One row of the favorites report") {
event_id: Int!
title: String!
favorite_count: Int!
}Declaring the ACL resource
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="Mironsoft_Event::event" title="Event Management">
<resource id="Mironsoft_Event::event_report"
title="View Favorites Report"/>
</resource>
</resource>
</resources>
</acl>
</config>This declaration follows the exact same ACL convention as every admin grid in the Admin Grids & Forms series - GraphQL doesn't bring its own permission system here, it fully reuses Magento's regular ACL meant for the admin area.
Checking the resolver against the admin token
<?php
declare(strict_types=1);
namespace Mironsoft\Event\Model\Resolver;
use Magento\Authorization\Model\UserContextInterface;
use Magento\Framework\Authorization\AuthorizationInterface;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
/**
* Resolves the eventFavoritesReport query field, restricted to admin users
* holding the Mironsoft_Event::event_report ACL resource.
*/
class EventFavoritesReport implements ResolverInterface
{
private const ACL_RESOURCE = 'Mironsoft_Event::event_report';
/**
* @param AuthorizationInterface $authorization Checks ACL resources for the current admin user
*/
public function __construct(
private readonly AuthorizationInterface $authorization,
) {
}
/**
* Returns favorite counts per event, restricted to authorized admin users.
*
* @param Field $field Resolved GraphQL field configuration
* @param mixed $context Resolver context, expected to carry an admin token
* @param ResolveInfo $info GraphQL resolve tree info
* @param array|null $value Parent resolver's value, unused for a top-level field
* @param array|null $args Arguments passed to this field
* @return array<int, array<string, mixed>>
* @throws GraphQlAuthorizationException
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
?array $value = null,
?array $args = null
): array {
if ($context->getUserType() !== UserContextInterface::USER_TYPE_ADMIN
|| !$this->authorization->isAllowed(self::ACL_RESOURCE)
) {
throw new GraphQlAuthorizationException(
__('You are not authorized to view the favorites report.')
);
}
// ... load report data and return it as EventFavoritesReportItem[]
return [];
}
}AuthorizationInterface::isAllowed() is the same class that regular admin controllers and menu items (acl.xml in the Admin Grids & Forms series) use for their own access checks - GraphQL taps into exactly the same infrastructure here, instead of inventing its own.
An admin token instead of a customer token
Calling such a query needs an admin token instead of a customer token in the Authorization header - generated e.g. through a Magento integration or the regular admin login endpoint of the REST API. For the storefront-typical customer mutations from chapters 16-18, this path is irrelevant; it only pays off for internal, admin-consumed GraphQL endpoints like this report.
Achtung: A GraphQL endpoint with an admin ACL check is still reachable through the publicly accessible /graphql URL - there's no separate, IP-restricted "admin GraphQL" endpoint. Protection consists entirely of the token validation and the isAllowed() check itself; forgetting that check exposes internal data under the very same URL as the public product catalog.
With the N+1 solution, caching, uploads, and ACL in place, block 6 is complete - the events API is performant, cacheable, media-capable, and can be secured granularly. Block 7 wraps up the series with testing strategies, debugging practice, and a summarizing cheat sheet.