Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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.

app/code/Mironsoft/Event/etc/schema.graphqls
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

app/code/Mironsoft/Event/etc/acl.xml
<?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

app/code/Mironsoft/Event/Model/Resolver/EventFavoritesReport.php
<?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.