Magento 2 REST API Returns 401 or 403? What Developers Should Check First
Find where the request fails—from credentials and OAuth signatures to route permissions, infrastructure, and the browser.
A Magento integration worked yesterday. Today, the same request returns 401 Unauthorized or 403 Forbidden. Regenerating the token feels like the obvious fix, but the failure might come from an incorrect permission, a changed authentication setting, a redirect, or a firewall that never forwarded the request to Magento.
The fastest investigation starts by identifying which component rejected the request, which identity the client supplied, and which resource that identity should access. This guide follows that sequence with practical examples you can adapt to your environment.
1. Read the response before replacing credentials
HTTP defines 401 as missing or invalid authentication credentials for the target resource. A 403 means the server understood the request but refuses to fulfill it.[2] Those definitions guide the investigation; they do not identify which server produced the response.
Capture the HTTP status, response headers, content type, response body, timestamp, and request identifier. A Magento-style JSON error mentioning a resource suggests an application authorization path. An HTML block page with an edge reference suggests an infrastructure policy. Neither appearance alone proves the source.
Do not assume an authorization message proves that authentication succeeded. Adobe documents an IMS environment issue where a resource-authorization message accompanies a 401 response.[3] Establish the resolved identity before widening permissions.
2. Reproduce one small, read-only request
Start with a controlled GET request instead of replaying an order submission. The following Bash example uses a customer token with /V1/customers/me. It is an identity-specific probe, not a universal test for administrator or integration credentials.
#!/usr/bin/env bash
set +x
umask 077
MAGENTO_API_URL='https://store.example.com/rest/default/V1/customers/me'
read -r -s -p 'Customer token: ' MAGENTO_CUSTOMER_TOKEN
printf '\n'
printf 'Authorization: Bearer %s\n' "$MAGENTO_CUSTOMER_TOKEN" |
curl --silent --show-error \
--connect-timeout 10 --max-time 30 \
--header @- \
--header 'Accept: application/json' \
--dump-header api-response.headers \
--output api-response.body \
--write-out 'HTTP status: %{http_code}\n' \
"$MAGENTO_API_URL"
probe_exit=${PIPESTATUS[1]}
unset MAGENTO_CUSTOMER_TOKEN
printf 'curl exit code: %s\n' "$probe_exit"Replace the host and store code with your environment. Enter the raw token without JSON quotation marks. The example reads the secret interactively and passes the header through standard input, rather than placing it in curl’s arguments. The response files can contain personal information, so keep them within the investigation.
The command preserves the response body and does not follow redirects. Curl supports separate header and body output; its normal exit code alone does not classify HTTP errors unless configured accordingly.[4] Inspect the recorded HTTP status and any transport error together.
rg -n -i \
'^(HTTP/|content-type:|location:|www-authenticate:|x-request-id:|x-correlation-id:|x-cache:)' \
api-response.headersCompare this result with the failing application using the same destination and credential type. Change one variable at a time. Otherwise, a successful test may simply be using a different account, store, route, or network path.
Record the last successful call and the first known failure separately. Compare that window with deployments, secret rotation, role changes, and network configuration updates. An incident reported immediately after a release may have started earlier. Preserve a sanitized request example so another developer can reproduce the same conditions instead of reconstructing them from memory.
3. Match the credential to the intended identity
Magento distinguishes customer, administrator, integration, and guest access. A customer token cannot become an administrative credential merely because its value is valid. Resource access depends on the identity and the route’s declared permissions.[5]
| Caller | Expected access | Useful evidence |
|---|---|---|
| Customer | Customer-owned or anonymous resources | Correct customer and ownership context |
| Administrator | Resources permitted by the Admin role | Account status and role assignment |
| Integration | Resources granted to that integration | Activation, credentials, and API resource selection |
| Guest | Routes allowing anonymous access | Whether authentication is required at all |
Admin and customer tokens have configurable lifetimes; Adobe documents defaults of four hours and one hour respectively.[6] Inspect the effective configuration and issuance history rather than assuming a copied token remains usable indefinitely.
For Admin token acquisition, follow the configured authentication flow, including the supported two-factor provider where applicable.[6] Separate failure to obtain a token from failure to use an existing token. They are different requests with different diagnostic evidence.
Treat environment identity as part of the credential record. Similar hostnames and copied configuration make staging and production easy to confuse. A token accepted by one deployment does not demonstrate access to another. Check the client’s active environment variables and configuration precedence, including settings injected when a long-running worker starts.
4. Check the integration authentication mode
An integration’s access token is not automatically interchangeable with a customer bearer token. Adobe documents that using integration access tokens as standalone bearer tokens is disabled by default. Integrations normally use their complete OAuth credential set to sign requests.[6]
Run Magento CLI commands from the active application release as the filesystem owner, and record the currently installed version alongside the observed configuration value.[13]
# Run as the Magento filesystem owner.
php bin/magento --version
php bin/magento config:show oauth/consumer/enable_integration_as_bearerThis command inspects the setting; it does not enable it. If a connector stopped after an upgrade, compare its documented authentication method with the current deployment configuration. Update the connector’s authentication implementation instead of treating a broader access setting as the universal repair.
Inspect the integration in System → Extensions → Integrations. Confirm its activation and selected API resources.[7] Ensure that the deployed client uses credentials from this integration in this environment, rather than an old secret retained by a worker.
Record credential versions or secret-manager references without recording secret values. When rotating credentials, update every relevant worker and scheduled process. A mixed deployment can produce intermittent failures because only some instances received the replacement credentials.
Separate temporary developer access from the unattended integration’s permanent identity. If a request succeeds with a privileged administrator but fails with the connector’s credentials, that comparison narrows the problem; it does not justify deploying the administrator’s token. Determine the missing capability and whether the integration actually needs it.
5. Compare the signed request with the transmitted request
For OAuth 1.0a integrations, the signature depends on the request representation. Adobe’s documentation specifies HMAC-SHA256 and includes the HTTP method, URL, OAuth parameters, timestamp, and nonce in the signing process.[8] A credential can be correct while the signature is wrong.
Use a client implementation that supports Magento’s required signature method. Compare the URL used during signing with the final URL sent over the network. Look for changed hostnames, rewritten schemes, query encoding differences, and additional query parameters introduced after signing.
Generate a fresh signature for each attempt. Reusing a captured Authorization header can reuse its nonce and timestamp. Avoid logging complete signed headers or secrets while comparing requests. Log sanitized request metadata and the selected signature method instead.
When a minimal signed GET works, introduce the original query parameters incrementally in a test environment. If failure appears only after a particular parameter is added, compare its encoded representation on both sides. Preserve repeated parameters and distinguish encoding from application values; visually similar URLs can represent different inputs to the signer.
6. Trace the route to its ACL resource
Once identity is established, inspect the exact route and HTTP method in webapi.xml. Its resource reference determines the permission required. The referenced ACL resource is defined in acl.xml and appears in the permissions tree.[5]
rg -n -g 'webapi.xml' -g 'acl.xml' \
'V1/inventory-bridge/status|Acme_InventoryBridge::status' \
app/code vendorThese illustrative files belong to a custom module. They demonstrate matching permission identifiers, not a complete installable extension. The service interface, implementation, dependency injection configuration, and module registration must already exist.[14]
<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
<route url="/V1/inventory-bridge/status" method="GET">
<service class="Acme\InventoryBridge\Api\StatusInterface"
method="getStatus"/>
<resources>
<resource ref="Acme_InventoryBridge::status"/>
</resources>
</route>
</routes><?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="Acme_InventoryBridge::status"
title="Read Inventory Bridge Status"
sortOrder="90"/>
</resource>
</resources>
</acl>
</config>Grant the precise resource needed by the integration. Avoid changing a protected route to anonymous or granting every resource simply to remove the error. A successful response obtained by removing authorization does not demonstrate that the intended access design works.
For customer-facing operations, route-level access is only part of the design. Verify record ownership inside the service path and use the appropriate authenticated customer context. A caller-supplied identifier must not silently select another customer’s private data.
Inspect the deployed files rather than only the repository branch. The running release may contain an older route definition or a customization from another module. If configuration changed, deploy it through the normal release process and verify the effective behavior afterward. Editing vendor files directly creates a fragile fix that an update can overwrite.
7. Inspect URL routing and header forwarding
Verify the deployment’s base path, store code, API version, and method. Conventional REST URLs use the store context in the path; a valid token cannot repair a request sent to the wrong environment or route.[9] Compare against the intended endpoint definition.
Inspect redirects before following them. Curl normally withholds Authorization when following redirects to another origin.[4] Correct the canonical destination instead of forwarding credentials indiscriminately to a redirected host.
Check whether the reverse proxy and application receive an Authorization header. This diagnostic fragment assumes injected Magento request and PSR logger objects; it records presence and method without recording the credential.
// Assumes these dependencies already exist in your diagnostic service:
/** @var \Magento\Framework\Webapi\Rest\Request $request */
/** @var \Psr\Log\LoggerInterface $logger */
$authorization = $request->getHeader('Authorization');
$logger->debug('REST authentication header presence', [
'authorization_present' => is_string($authorization)
&& $authorization !== '',
'method' => $request->getMethod(),
]);
// Never log the header value. Remove temporary diagnostics afterward.A present header is not proof of a valid token. If the client sent authentication but the application sees none, investigate forwarding, rewrites, and any staging authentication gateway. Apply configuration changes through the hosting workflow, then repeat the same controlled request.
Browser session success is another misleading comparison. A storefront widget can have session context that a backend connector does not possess.[12] Reproduce the connector’s actual authentication mechanism instead of treating an already logged-in browser as proof that the external request should succeed.
8. Separate WAF rejection from Magento authorization
A firewall can reject a request before Magento evaluates its token. Adobe documents Fastly WAF block responses that return 403 and include a reference identifier.[10] Preserve that identifier with the timestamp, hostname, method, and a sanitized description of the request.
Correlate edge events with application and web-server access logs. Missing application evidence is useful, but incomplete logging means it is not conclusive. Ask the infrastructure owner to identify the matching rule and determine whether the request was forwarded.
If a legitimate payload triggers a false positive, narrow the investigation to the specific route and rule. Do not disable the entire WAF or expose the origin publicly as a debugging shortcut. Validate any exception against both the intended request and requests that should remain blocked.
Keep network policy separate from Magento ACL. An integration can have the correct resource grant and still fail an IP restriction. Likewise, permitting its network traffic does not grant access to protected application data.
9. Investigate browser-only failures through preflight
If curl succeeds but browser JavaScript fails, inspect the browser Network panel. Cross-origin requests with Authorization can require an OPTIONS preflight before the actual API call. A rejected preflight can prevent that call from being sent.[11]
curl --silent --show-error --include \
--connect-timeout 10 --max-time 30 \
--request OPTIONS \
--header 'Origin: https://shopfront.example.com' \
--header 'Access-Control-Request-Method: GET' \
--header 'Access-Control-Request-Headers: authorization' \
'https://store.example.com/rest/default/V1/customers/me'Replace both origins with the real deployment values. This command inspects the server’s preflight response; curl does not enforce browser CORS policy. Verify the allowed origin, method, and request headers. Authorization must be explicitly allowed rather than relying on a wildcard header allowance.[11]
Inspect the actual response too: an API error without appropriate CORS response headers may appear to JavaScript as a generic network failure. Fix the policy at the responsible gateway or application layer. Changing fetch to no-cors does not provide usable access to a protected JSON API.
10. Prove that the intended access now works
Repeat the original request after the smallest targeted change. Confirm the correct identity, store context, response data, and business outcome. Then perform a negative test: an invalid token and an identity without the required permission must still fail.
For ownership-sensitive endpoints, verify that one test customer cannot retrieve another customer’s data. For integrations, verify that unrelated protected operations remain inaccessible. These tests distinguish a repaired authentication path from an accidentally broadened permission boundary.
Keep token acquisition, API authorization, edge rejection, and browser preflight as separate monitoring categories. Avoid endless retries of unchanged credentials, and do not replay state-changing requests until their previous outcome is understood. A clear incident record should explain what rejected the request, what changed, and how both permitted and prohibited access were verified.