├── Api └── CorsCheckInterface.php ├── LICENSE ├── Model └── CorsCheck.php ├── Plugin ├── CorsHeadersPlugin.php ├── CorsRequestMatchPlugin.php └── CorsRequestOptionsPlugin.php ├── README.md ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ └── system.xml ├── config.xml ├── di.xml ├── module.xml └── webapi.xml └── registration.php /Api/CorsCheckInterface.php: -------------------------------------------------------------------------------- 1 | " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. 49 | -------------------------------------------------------------------------------- /Model/CorsCheck.php: -------------------------------------------------------------------------------- 1 | response = $response; 28 | $this->request = $request; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function check() 35 | { 36 | // respond to OPTIONS request with appropriate headers 37 | $this->response->setHeader('Access-Control-Allow-Methods', $this->request->getHeader('Access-Control-Request-Method'), true); 38 | $this->response->setHeader('Access-Control-Allow-Headers', $this->request->getHeader('Access-Control-Request-Headers'), true); 39 | return ''; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Plugin/CorsHeadersPlugin.php: -------------------------------------------------------------------------------- 1 | response = $response; 41 | $this->scopeConfig = $scopeConfig; 42 | } 43 | 44 | /** 45 | * Get the origin domain the requests are going to come from 46 | * @return string 47 | */ 48 | protected function getOriginUrl() 49 | { 50 | return $this->scopeConfig->getValue('web/corsRequests/origin_url', 51 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE); 52 | } 53 | 54 | /** 55 | * Get the origin domain the requests are going to come from 56 | * @return string 57 | */ 58 | protected function getAllowCredentials() 59 | { 60 | return (bool) $this->scopeConfig->getValue('web/corsRequests/allow_credentials', 61 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE); 62 | } 63 | 64 | /** 65 | * Get the origin domain the requests are going to come from 66 | * @return string 67 | */ 68 | protected function getEnableAmp() 69 | { 70 | return (bool) $this->scopeConfig->getValue('web/corsRequests/enable_amp', 71 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE); 72 | } 73 | 74 | /** 75 | * Get the Access-Control-Max-Age 76 | * @return string 77 | */ 78 | protected function getMaxAge() 79 | { 80 | return (int) $this->scopeConfig->getValue('web/corsRequests/max_age', 81 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE); 82 | } 83 | 84 | /** 85 | * Triggers before original dispatch 86 | * This method triggers before original \Magento\Webapi\Controller\Rest::dispatch and set version 87 | * from request params to VersionManager instance 88 | * @param FrontControllerInterface $subject 89 | * @param RequestInterface $request 90 | * @return void 91 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 92 | */ 93 | public function beforeDispatch( 94 | FrontControllerInterface $subject, 95 | RequestInterface $request 96 | ) { 97 | if ($originUrl = $this->getOriginUrl()) { 98 | $this->response->setHeader('Access-Control-Allow-Origin', rtrim($originUrl,"/"), true); 99 | if ($this->getAllowCredentials()) { 100 | $this->response->setHeader('Access-Control-Allow-Credentials', 'true', true); 101 | } 102 | if ($this->getEnableAmp()) { 103 | $this->response->setHeader('AMP-Access-Control-Allow-Source-Origin', rtrim($originUrl,"/"), true); 104 | } 105 | if ((int)$this->getMaxAge() > 0) { 106 | $this->response->setHeader('Access-Control-Max-Age', $this->getMaxAge(), true); 107 | } 108 | } 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /Plugin/CorsRequestMatchPlugin.php: -------------------------------------------------------------------------------- 1 | request = $request; 41 | $this->routeFactory = $routeFactory; 42 | } 43 | 44 | /** 45 | * Generate the list of available REST routes. Current HTTP method is taken into account. 46 | * 47 | * @param \Magento\Webapi\Model\Rest\Config $subject 48 | * @param $proceed 49 | * @param Request $request 50 | * @return \Magento\Webapi\Controller\Rest\Router\Route 51 | * @throws \Magento\Framework\Webapi\Exception 52 | */ 53 | public function aroundMatch( 54 | Router $subject, 55 | callable $proceed, 56 | Request $request 57 | ) 58 | { 59 | try { 60 | $returnValue = $proceed($request); 61 | } catch (\Magento\Framework\Webapi\Exception $e) { 62 | $requestHttpMethod = $this->request->getHttpMethod(); 63 | if ($requestHttpMethod == 'OPTIONS') { 64 | return $this->createRoute(); 65 | } else { 66 | throw $e; 67 | } 68 | } 69 | return $returnValue; 70 | } 71 | 72 | /** 73 | * Create route object to the placeholder CORS route. 74 | * 75 | * @return \Magento\Webapi\Controller\Rest\Router\Route 76 | */ 77 | protected function createRoute() 78 | { 79 | /** @var $route \Magento\Webapi\Controller\Rest\Router\Route */ 80 | $route = $this->routeFactory->createRoute( 81 | 'Magento\Webapi\Controller\Rest\Router\Route', 82 | '/V1/cors/check' 83 | ); 84 | 85 | $route->setServiceClass('SplashLab\CorsRequests\Api\CorsCheckInterface') 86 | ->setServiceMethod('check') 87 | ->setSecure(false) 88 | ->setAclResources(['anonymous']) 89 | ->setParameters([]); 90 | 91 | return $route; 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /Plugin/CorsRequestOptionsPlugin.php: -------------------------------------------------------------------------------- 1 | isGet() && !$subject->isPost() && !$subject->isPut() && !$subject->isDelete() && !$subject->isOptions()) { 32 | throw new InputException(__('Request method is invalid.')); 33 | } 34 | return $subject->getMethod(); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magento 2 CORS Cross-Domain Requests by SplashLab 2 | 3 | This module allows you to enable Cross-Origin Resource Sharing (CORS) REST API requests in Magento 2 by adding the appropriate HTTP headers and handling the pre-flight OPTIONS requests. 4 | 5 | This can be used to allow AJAX and other requests to the Magento 2 REST API from another domain (or subdomain). 6 | 7 | ## How to install 8 | 9 | ### 1. via composer 10 | 11 | Edit `composer.json` 12 | 13 | ``` 14 | { 15 | "repositories": [ 16 | { 17 | "type": "vcs", 18 | "url": "https://github.com/splashlab/magento-2-cors-requests" 19 | } 20 | ], 21 | "require": { 22 | "splashlab/magento-2-cors-requests": "dev-master" 23 | } 24 | } 25 | ``` 26 | 27 | ``` 28 | composer install 29 | php bin/magento setup:upgrade 30 | php bin/magento setup:static-content:deploy 31 | ``` 32 | 33 | ### 2. Copy and paste 34 | 35 | Download latest version from GitHub 36 | 37 | Paste into `app/code/SplashLab/CorsRequests` directory 38 | 39 | ``` 40 | php bin/magento setup:upgrade 41 | php bin/magento setup:static-content:deploy 42 | ``` 43 | 44 | ### 3. Update Origin URL 45 | 46 | In `Stores -> Configuration`, go to `General -> Web -> CORS Requests Configuration`. 47 | 48 | Then edit the the `CORS Origin Url` field to the domain you want to enable cross-domain requests from. (i.e. http://example.com) 49 | 50 | ## How does it work? 51 | 52 | The full implementation of CORS cross-domain HTTP requests is outside the scope of this README, but this is what this module does: 53 | 54 | 1. Allows onfigureing an Origin Url in the Admin Configuration area - this is the domain which cross-domain requests are permitted from 55 | 2. This domain is added to a `Access-Control-Allow-Origin` response HTTP header 56 | 3. Optionally you can enable the `Access-Control-Allow-Credentials` header as well, to enable passing cookies 57 | 58 | For non-GET and non-standard-POST requests (i.e. PUT and DELETE), the "pre-flight check" OPTIONS request is handled by: 59 | 60 | 1. An empty `/V1/cors/check` API response with the appropriate headers: 61 | 2. `Access-Control-Allow-Methods` response header, which mirrors the `Access-Control-Request-Method` request header 62 | 3. `Access-Control-Allow-Headers` response header, which mirrors the `Access-Control-Request-Headers` request header 63 | 64 | ### Alternative Solutions 65 | 66 | You can also manage these CORS headers with Apache and Nginx rules, instead of using this extension: 67 | 68 | - https://community.magento.com/t5/Magento-2-Feature-Requests-and/API-CORS-requests-will-fail-without-OPTIONS-reponse/idi-p/60551 69 | - https://stackoverflow.com/questions/35174585/how-to-add-cors-cross-origin-policy-to-all-domains-in-nginx 70 | 71 | But I created this extension to allow you to configure the Origin domain the Admin Configuration, and to avoid having to create and manage special server configuration. 72 | 73 | ## CORS Cross-Domain Request References 74 | 75 | - https://en.wikipedia.org/wiki/Cross-origin_resource_sharing 76 | - https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS 77 | - https://www.html5rocks.com/en/tutorials/cors/ 78 | - https://stackoverflow.com/questions/29954037/how-to-disable-options-request 79 | - https://stackoverflow.com/questions/12320467/jquery-cors-content-type-options 80 | - https://github.com/magento/magento2/issues/8399 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lpouwelse/magento-2-cors-requests", 3 | "description": "Enabling cross-origin resource sharing (CORS) requests to Magento 2 API from configured Origin domain", 4 | "homepage": "https://github.com/splashlab/magento-2-cors-requests", 5 | "type": "magento2-module", 6 | "version": "100.0.7", 7 | "license": [ 8 | "OSL-3.0", 9 | "AFL-3.0" 10 | ], 11 | "support": { 12 | "issues": "https://github.com/splashlab/magento-2-cors-requests/issues", 13 | "source": "https://github.com/splashlab/magento-2-cors-requests" 14 | }, 15 | "require": { 16 | "php": "~7.1.3||~7.2.0||~7.3.0||~7.4.0||~8.1.0", 17 | "magento/framework": "*" 18 | }, 19 | "autoload": { 20 | "files": [ 21 | "registration.php" 22 | ], 23 | "psr-4": { 24 | "SplashLab\\CorsRequests\\": "" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 |
6 | SplashLab_CorsRequests::config 7 | 9 | 10 | 12 | 13 | *, or fully qualified URLs without trailing '/' (slash) (e.g. http://example.com) 14 | 15 | 16 | 18 | 19 | Magento\Config\Model\Config\Source\Yesno 20 | Enables Access-Control-Allow-Credentials response header to pass cookies 21 | 22 | 24 | 25 | Magento\Config\Model\Config\Source\Yesno 26 | Enables AMP-Access-Control-Allow-Source-Origin response header for AMP CORS requests 27 | 28 | 30 | 31 | Enables Access-Control-Max-Age response header for CORS requests (max age in seconds) 32 | 33 | 34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | * 8 | 0 9 | 0 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /etc/webapi.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 |