Magento 2 REST API is powerful but exposed to abuse if not properly secured. Default token authentication, over-permissive ACL resources, no rate limiting, and logging gaps are the most common issues I see on production installations. I show a layered security approach: proper token management, granular ACL, nginx rate limiting, and monitoring for suspicious patterns.
Authentication tokens – the basics done right
# Generate customer token
curl -X POST https://shop.example.com/rest/V1/integration/customer/token \
-H 'Content-Type: application/json' \
-d '{"username": "customer@example.com", "password": "password123"}'
# Generate admin token (NEVER use in frontend code!)
curl -X POST https://shop.example.com/rest/V1/integration/admin/token \
-H 'Content-Type: application/json' \
-d '{"username": "admin", "password": "admin_password"}'
# Use the token
curl -X GET https://shop.example.com/rest/V1/customers/me \
-H 'Authorization: Bearer eyJraWQiOiIxIiwiYWxnIjoiSFMyNTYifQ...'
<?php
// Programmatic token generation with expiry management
class ApiTokenService
{
public function __construct(
private \Magento\Integration\Model\Oauth\TokenFactory $tokenFactory,
private \Magento\Integration\Model\ResourceModel\Oauth\Token $tokenResource,
private \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
) {}
public function generateCustomerToken(int $customerId): string
{
$customer = $this->customerRepository->getById($customerId);
$token = $this->tokenFactory->create();
$token->createCustomerToken($customerId);
$this->tokenResource->save($token);
return $token->getToken();
}
public function revokeCustomerTokens(int $customerId): void
{
// Revoke all active tokens for a customer (e.g. on password change)
$this->tokenResource->revokeCustomerTokens([$customerId]);
}
}
Granular ACL – least privilege principle
<!-- etc/acl.xml - define fine-grained permissions -->
<?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="Vendor_Module::root" title="Vendor Module" sortOrder="999">
<resource id="Vendor_Module::read"
title="Read products" sortOrder="10"/>
<resource id="Vendor_Module::write"
title="Create/update products" sortOrder="20"/>
<resource id="Vendor_Module::delete"
title="Delete products" sortOrder="30"/>
</resource>
</resource>
</resources>
</acl>
</config>
<!-- etc/webapi.xml - assign ACL to endpoints -->
<route url="/V1/vendor-module/products" method="GET">
<service class="Vendor\Module\Api\ProductRepositoryInterface" method="getList"/>
<resources>
<resource ref="Vendor_Module::read"/> <!-- requires read permission only -->
</resources>
</route>
<route url="/V1/vendor-module/products" method="POST">
<service class="Vendor\Module\Api\ProductRepositoryInterface" method="save"/>
<resources>
<resource ref="Vendor_Module::write"/> <!-- requires write permission -->
</resources>
</route>
<route url="/V1/vendor-module/products/:id" method="DELETE">
<service class="Vendor\Module\Api\ProductRepositoryInterface" method="deleteById"/>
<resources>
<resource ref="Vendor_Module::delete"/> <!-- requires delete permission -->
</resources>
</route>
nginx rate limiting – protect the token endpoint
# /etc/nginx/conf.d/magento.conf
# Rate limit zone: 5 requests per second per IP for auth endpoints
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/s;
# Rate limit zone: 100 req/s per IP for API endpoints
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
server {
# ...
# Strict rate limit on token endpoints - brute force protection
location ~ ^/rest/V1/integration/(admin|customer)/token {
limit_req zone=auth burst=10 nodelay;
limit_req_status 429;
# Optional: log brute force attempts
access_log /var/log/nginx/auth_access.log combined;
# ... proxy to PHP-FPM
fastcgi_pass fastcgi_backend;
include fastcgi_params;
}
# General API rate limiting
location /rest/ {
limit_req zone=api burst=200 nodelay;
limit_req_status 429;
# Add rate limit headers
add_header X-RateLimit-Limit 100;
add_header Retry-After 1;
fastcgi_pass fastcgi_backend;
include fastcgi_params;
}
}
Monitoring suspicious patterns
# Detect brute force on token endpoint
awk '/POST.*\/rest\/V1\/integration.*\/token/ {print $1}' /var/log/nginx/access.log | \
sort | uniq -c | sort -rn | head -20
# IP with 100+ attempts in last hour = suspected brute force
# Find IPs hitting the API without auth (401 responses)
awk '$9 == "401" {print $1}' /var/log/nginx/access.log | \
sort | uniq -c | sort -rn | head -20
# Monitor for unusually large responses (data exfiltration indicator)
awk '$10 > 1000000 {print $0}' /var/log/nginx/access.log | \
grep "rest/V1" | tail -20
# Automated alert: block IPs with 100+ 401s in 5 minutes
# (can integrate with fail2ban or cloudflare)
<?php
// Magento plugin: log all API authentication failures
namespace Vendor\Security\Plugin;
class ApiAuthLogger
{
public function __construct(private \Psr\Log\LoggerInterface $logger) {}
public function afterAuthenticate(
\Magento\Webapi\Model\Authorization\TokenUserContext $subject,
$result
) {
if ($subject->getUserId() === null) {
$this->logger->warning('API auth failure', [
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'uri' => $_SERVER['REQUEST_URI'] ?? '',
'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
]);
}
return $result;
}
}
Summary
API security in Magento 2 is layered: token management at the application level, granular ACL on each endpoint, nginx rate limiting at the web server level, and monitoring to detect patterns before they become incidents. The most impactful single change is nginx rate limiting on the token endpoint – it stops brute force credential attacks that bypass application-level protections. Combine it with fail2ban to automatically block offending IPs.
