Skip to content
intermediate Phase 72 · Security Advanced

API Security

45m
1 problems
Topic Progress 0%

Token Lifecycle Management

Token Creation and Storage

use Magento\Integration\Model\TokenFactory;
use Magento\Framework\Encryption\EncryptorInterface;

class TokenManager
{
    private $tokenFactory;
    private $encryptor;

    public function __construct(
        TokenFactory $tokenFactory,
        EncryptorInterface $encryptor
    ) {
        $this->tokenFactory = $tokenFactory;
        $this->encryptor = $encryptor;
    }

    public function createToken($consumerId, $accessToken, $secret)
    {
        $token = $this->tokenFactory->create();
        $token->setConsumerId($consumerId);
        $token->setAccessToken($this->encryptor->hash($accessToken));
        $token->setSecret($this->encryptor->hash($secret));
        $token->setCreatedAt(gmdate('Y-m-d H:i:s'));
        $token->save();

        return $token;
    }

    public function revokeToken($tokenId)
    {
        $token = $this->tokenFactory->create()->load($tokenId);
        $token->delete();
    }
}

Token Expiration

// Check token expiration
public function isTokenExpired($token)
{
    $createdAt = strtotime($token->getCreatedAt());
    $lifetime = $this->config->getTokenLifetime();
    
    return (time() - $createdAt) > $lifetime;
}

// Token refresh flow
public function refreshToken($refreshToken)
{
    $existingToken = $this->validateRefreshToken($refreshToken);
    
    if (!$existingToken || $this->isTokenExpired($existingToken)) {
        throw new \Exception('Invalid or expired refresh token');
    }

    $newToken = $this->generateAccessToken();
    $existingToken->setAccessToken($this->encryptor->hash($newToken));
    $existingToken->save();

    return $newToken;
}

Key Points

  • Store tokens encrypted at rest
  • Implement token expiration (4 hours for admin tokens)
  • Support token revocation for security incidents
  • Use refresh tokens for long-lived access

Rate Limiting Configuration

Magento Rate Limiting

use Magento\Framework\App\Response\HttpInterface;
use Magento\Framework\Cache\FrontendInterface;

class RateLimiter
{
    private $cache;
    private $config;

    public function __construct(
        FrontendInterface $cache,
        ConfigInterface $config
    ) {
        $this->cache = $cache;
        $this->config = $config;
    }

    public function checkRateLimit($identifier, $maxRequests, $windowSeconds)
    {
        $cacheKey = 'rate_limit_' . $identifier;
        $data = $this->cache->load($cacheKey);
        
        if ($data) {
            $requests = unserialize($data);
            $windowStart = time() - $windowSeconds;
            
            // Remove old requests
            $requests = array_filter($requests, function($timestamp) use ($windowStart) {
                return $timestamp > $windowStart;
            });
        } else {
            $requests = [];
        }

        if (count($requests) >= $maxRequests) {
            return false; // Rate limit exceeded
        }

        $requests[] = time();
        $this->cache->save(serialize($requests), $cacheKey, [], $windowSeconds);
        
        return true;
    }
}

Rate Limit Headers

public function addRateLimitHeaders($response, $limit, $remaining, $reset)
{
    $response->setHeader('X-RateLimit-Limit', $limit);
    $response->setHeader('X-RateLimit-Remaining', $remaining);
    $response->setHeader('X-RateLimit-Reset', $reset);
    
    if ($remaining <= 0) {
        $response->setHeader('Retry-After', $reset - time());
    }
}

Key Points

  • Rate limit by IP address or API key
  • Return 429 Too Many Requests when exceeded
  • Include rate limit headers in responses
  • Consider different limits for different endpoints

CORS Configuration

Magento CORS Setup

use Magento\Framework\App\Response\HeaderProviderInterface;

class CorsHeaders implements HeaderProviderInterface
{
    private $config;

    public function __construct(ConfigInterface $config)
    {
        $this->config = $config;
    }

    public function getHeaders()
    {
        $allowedOrigins = $this->config->getAllowedOrigins();
        $origin = $_SERVER['HTTP_ORIGIN'] ?? '';

        if (in_array($origin, $allowedOrigins)) {
            return [
                'Access-Control-Allow-Origin' => $origin,
                'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
                'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With',
                'Access-Control-Allow-Credentials' => 'true',
                'Access-Control-Max-Age' => '86400',
            ];
        }

        return [];
    }
}

GraphQL CORS

// graphql.xml configuration
<config>
    <router id="standard">
        <route url="/graphql" frontName="graphql" />
    </router>
    
    <!-- CORS for GraphQL endpoint -->
    <cors>
        <allowed_origins>
            <origin>https://store.example.com</origin>
            <origin>https://admin.example.com</origin>
        </allowed_origins>
    </cors>
</config>

Key Points

  • Never use Access-Control-Allow-Origin: * in production
  • Validate Origin header before allowing access
  • Handle preflight OPTIONS requests
  • Limit allowed methods and headers

OAuth 2.0 Implementation

OAuth 2.0 Flow

// Authorization Code Grant
public function authorize($clientId, $redirectUri, $scope)
{
    $client = $this->validateClient($clientId, $redirectUri);
    
    if (!$client) {
        throw new \Exception('Invalid client');
    }

    $code = $this->generateAuthorizationCode($client, $scope);
    
    return $this->redirect($redirectUri . '?code=' . $code);
}

public function token($grantType, $code, $clientId, $clientSecret)
{
    if ($grantType !== 'authorization_code') {
        throw new \Exception('Unsupported grant type');
    }

    $client = $this->validateClient($clientId, $clientSecret);
    $authCode = $this->validateAuthorizationCode($code, $client);
    
    $accessToken = $this->generateAccessToken($client, $authCode['scope']);
    $refreshToken = $this->generateRefreshToken($client);

    return [
        'access_token' => $accessToken,
        'token_type' => 'Bearer',
        'expires_in' => 3600,
        'refresh_token' => $refreshToken,
    ];
}

Scopes and Permissions

// Define API scopes
$scopes = [
    'catalog' => ['read', 'write'],
    'orders' => ['read', 'write'],
    'customers' => ['read'],
    'inventory' => ['read', 'write'],
];

// Validate scope
public function hasScope($token, $requiredScope)
{
    $tokenScopes = explode(' ', $token->getScope());
    return in_array($requiredScope, $tokenScopes);
}

Key Points

  • Use HTTPS for all OAuth endpoints
  • Validate client credentials strictly
  • Implement short-lived access tokens
  • Support refresh token rotation

Practice Problems

0 / 1 solved
Rate Limiter Implementation

Implement a rate limiter that tracks requests per API key and returns proper headers.

Solution
class RateLimiter {
    private $cache;
    
    public function check($apiKey, $limit, $window) {
        $key = 'rl_' . $apiKey;
        $data = $this->cache->load($key);
        $requests = $data ? unserialize($data) : [];
        
        $windowStart = time() - $window;
        $requests = array_filter($requests, fn($t) => $t > $windowStart);
        
        if (count($requests) >= $limit) {
            return false;
        }
        
        $requests[] = time();
        $this->cache->save(serialize($requests), $key, [], $window);
        return true;
    }
    
    public function getHeaders($apiKey) {
        $remaining = $this->getRemainingRequests($apiKey);
        return [
            'X-RateLimit-Limit' => 100,
            'X-RateLimit-Remaining' => $remaining,
        ];
    }
}

Quiz

1. What HTTP status code indicates rate limit exceeded?

Question 1 options

2. Why not use Access-Control-Allow-Origin: *?

Question 2 options

3. What is the recommended admin token lifetime?

Question 3 options

4. How should API secrets be stored?

Question 4 options

Flashcards

Question

Rate limit status code?

Answer

429 Too Many Requests

Question

CORS wildcard risk?

Answer

Allows any origin to access API, security vulnerability

Question

Token storage best practice?

Answer

Encrypt at rest, store in environment variables

Question

OAuth 2.0 grant types?

Answer

Authorization Code, Client Credentials, Refresh Token

Revision Notes

Key Takeaways

  • 1. Implement token expiration and revocation
  • 2. Rate limit by IP or API key with proper headers
  • 3. CORS must validate specific origins, not wildcards
  • 4. OAuth 2.0 provides secure delegated access

Interview Tips

  • Explain token lifecycle management
  • Discuss rate limiting strategies
  • Know OAuth 2.0 flows and security considerations

Cheat Sheet

API Security

  • Tokens: encrypted, expired, revocable
  • Rate limit: 429 + headers
  • CORS: specific origins only
  • OAuth: authorization code flow
  • Always use HTTPS