diff --git a/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowCredentialsHeaderProvider.php b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowCredentialsHeaderProvider.php
new file mode 100755
index 0000000000000..d22dad79a59bf
--- /dev/null
+++ b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowCredentialsHeaderProvider.php
@@ -0,0 +1,85 @@
+corsConfiguration = $corsConfiguration;
+ $this->headerName = $headerName;
+ $this->requestValidator = $requestValidator;
+ }
+
+ /**
+ * Get name of header
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->headerName;
+ }
+
+ /**
+ * Check if header can be applied
+ *
+ * @return bool
+ */
+ public function canApply(): bool
+ {
+ return $this->requestValidator->isOriginAllowed() && $this->corsConfiguration->isCredentialsAllowed();
+ }
+
+ /**
+ * Get value for header
+ *
+ * @return string
+ */
+ public function getValue(): string
+ {
+ return self::ALLOW_CREDENTIALS;
+ }
+}
diff --git a/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowHeadersHeaderProvider.php b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowHeadersHeaderProvider.php
new file mode 100755
index 0000000000000..b8c7de1797241
--- /dev/null
+++ b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowHeadersHeaderProvider.php
@@ -0,0 +1,82 @@
+corsConfiguration = $corsConfiguration;
+ $this->headerName = $headerName;
+ $this->requestValidator = $requestValidator;
+ }
+
+ /**
+ * Get name of header
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->headerName;
+ }
+
+ /**
+ * Check if header can be applied
+ *
+ * @return bool
+ */
+ public function canApply(): bool
+ {
+ return $this->requestValidator->isOriginAllowed() && $this->getValue();
+ }
+
+ /**
+ * Get value for header
+ *
+ * @return string
+ */
+ public function getValue(): string
+ {
+ return $this->corsConfiguration->getAllowedHeaders()
+ ? implode(',', $this->corsConfiguration->getAllowedHeaders())
+ : '';
+ }
+}
diff --git a/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowMethodsHeaderProvider.php b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowMethodsHeaderProvider.php
new file mode 100755
index 0000000000000..50c41534c8de9
--- /dev/null
+++ b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowMethodsHeaderProvider.php
@@ -0,0 +1,87 @@
+corsConfiguration = $corsConfiguration;
+ $this->headerName = $headerName;
+ $this->requestValidator = $requestValidator;
+ }
+
+ /**
+ * Get name of header
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->headerName;
+ }
+
+ /**
+ * Check if header can be applied
+ *
+ * @return bool
+ */
+ public function canApply(): bool
+ {
+ return $this->requestValidator->isOriginAllowed() && $this->getValue();
+ }
+
+ /**
+ * Get value for header
+ *
+ * @return string
+ */
+ public function getValue(): string
+ {
+ return $this->corsConfiguration->getAllowedMethods()
+ ? implode(',', $this->corsConfiguration->getAllowedMethods())
+ : self::GRAPHQL_CORS_ALLOWED_METHODS;
+ }
+}
diff --git a/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowOriginHeaderProvider.php b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowOriginHeaderProvider.php
new file mode 100755
index 0000000000000..da484b144d6c8
--- /dev/null
+++ b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/AllowOriginHeaderProvider.php
@@ -0,0 +1,94 @@
+corsConfiguration = $corsConfiguration;
+ $this->headerName = $headerName;
+ $this->request = $request;
+ $this->requestValidator = $requestValidator;
+ }
+
+ /**
+ * Get name of header
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->headerName;
+ }
+
+ /**
+ * Check if header can be applied
+ *
+ * @return bool
+ */
+ public function canApply(): bool
+ {
+ return $this->requestValidator->isOriginAllowed() && $this->getValue();
+ }
+
+ public function getValue(): string
+ {
+ return $this->isAllOriginsAllowed() ? '*' : $this->request->getHeader('Origin');
+ }
+
+ /**
+ * if '*' is present, allow all origins
+ *
+ * @return bool
+ */
+ private function isAllOriginsAllowed(): bool
+ {
+ return in_array('*', $this->corsConfiguration->getAllowedOrigins());
+ }
+}
diff --git a/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/MaxAgeHeaderProvider.php b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/MaxAgeHeaderProvider.php
new file mode 100755
index 0000000000000..b7da996f72ce5
--- /dev/null
+++ b/app/code/Magento/GraphQl/Controller/Cors/HttpResponseHeaderProvider/MaxAgeHeaderProvider.php
@@ -0,0 +1,85 @@
+corsConfiguration = $corsConfiguration;
+ $this->headerName = $headerName;
+ $this->requestValidator = $requestValidator;
+ }
+
+ /**
+ * Get name of header
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->headerName;
+ }
+
+ /**
+ * Check if header can be applied
+ *
+ * @return bool
+ */
+ public function canApply(): bool
+ {
+ return $this->requestValidator->isOriginAllowed() && $this->getValue();
+ }
+
+ /**
+ * Get value for header
+ *
+ * @return string
+ */
+ public function getValue(): string
+ {
+ return $this->corsConfiguration->getMaxAge() ?? self::GRAPH_CORS_MAX_AGE_DEFAULT;
+ }
+}
diff --git a/app/code/Magento/GraphQl/Model/Cors/ConfigurationProvider.php b/app/code/Magento/GraphQl/Model/Cors/ConfigurationProvider.php
new file mode 100755
index 0000000000000..abc476483bfc0
--- /dev/null
+++ b/app/code/Magento/GraphQl/Model/Cors/ConfigurationProvider.php
@@ -0,0 +1,134 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * returns the list of allowed origins
+ *
+ * @return array|null
+ */
+ public function getAllowedOrigins(): ?array
+ {
+ return $this->processOptions($this->scopeConfig->getValue(self::GRAPHQL_CORS_ALLOWED_ORIGINS));
+ }
+
+ /**
+ * returns the list of allowed headers
+ *
+ * @return array|null
+ */
+ public function getAllowedHeaders(): ?array
+ {
+ return $this->processOptions($this->scopeConfig->getValue(self::GRAPHQL_CORS_ALLOWED_HEADERS));
+ }
+
+ /**
+ * returns the list of allowed methods
+ *
+ * @return array|null
+ */
+ public function getAllowedMethods(): ?array
+ {
+ return $this->processOptions($this->scopeConfig->getValue(self::GRAPHQL_CORS_ALLOWED_METHODS));
+ }
+
+ /**
+ * returns CORS max age value
+ *
+ * @return string
+ */
+ public function getMaxAge(): string
+ {
+ return $this->scopeConfig->getValue(self::GRAPHQL_CORS_MAX_AGE);
+ }
+
+ /**
+ * returns credentials value
+ *
+ * @return bool
+ */
+ public function isCredentialsAllowed(): bool
+ {
+ return $this->scopeConfig->isSetFlag(self::GRAPHQL_CORS_ALLOW_CREDENTIALS);
+ }
+
+ /**
+ * converts the comma separated values into array
+ *
+ * @param $option
+ * @param string $delimiter
+ * @return array
+ */
+ public function processOptions($option, $delimiter = ','): array
+ {
+ //if no config is provided in env.php
+ if(!$option){
+ $option = '';
+ }
+
+ $configurations = explode($delimiter, $option);
+ $trimmedConfigurations = array_map(
+ function ($trimmedConfiguration) {
+ return trim($trimmedConfiguration);
+ },
+ $configurations
+ );
+
+ $trimmedConfigurations = array_values(array_filter($trimmedConfigurations, function ($trimmedConfiguration) {
+ return !empty($trimmedConfiguration);
+ }));
+
+ return $trimmedConfigurations;
+ }
+}
diff --git a/app/code/Magento/GraphQl/Model/Cors/ConfigurationProviderInterface.php b/app/code/Magento/GraphQl/Model/Cors/ConfigurationProviderInterface.php
new file mode 100755
index 0000000000000..77a2b33b4de43
--- /dev/null
+++ b/app/code/Magento/GraphQl/Model/Cors/ConfigurationProviderInterface.php
@@ -0,0 +1,47 @@
+configuration = $configuration;
+ $this->request = $request;
+ }
+
+ /**
+ * Determines whether the requested origin is present in configuration
+ *
+ * @return bool
+ */
+ private function isOriginExistsInConfiguration(): bool
+ {
+ return in_array($this->request->getHeader('Origin'), $this->configuration->getAllowedOrigins());
+ }
+
+ /**
+ * Determines whether all origins should be allowed
+ *
+ * @return bool
+ */
+ private function isAllOriginsAllowed(): bool
+ {
+ return in_array('*', $this->configuration->getAllowedOrigins());
+ }
+
+ /**
+ * Determines whether the request is valid and applies CORS headers
+ * @return bool
+ */
+ public function isOriginAllowed(): bool
+ {
+ if ($this->request instanceof HttpRequest) {
+
+ if (!$this->originHeaderExists()) {
+ return false;
+ }
+
+ return $this->isAllOriginsAllowed() || $this->isOriginExistsInConfiguration();
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines whether an origin header exists
+ * @return bool
+ */
+ private function originHeaderExists(): bool
+ {
+ try {
+ return $this->request->getHeader('Origin') ? true : false;
+ } catch (\Exception $exception) {
+ return false;
+ }
+ }
+}
diff --git a/app/code/Magento/GraphQl/Model/Cors/Validator/RequestValidatorInterface.php b/app/code/Magento/GraphQl/Model/Cors/Validator/RequestValidatorInterface.php
new file mode 100755
index 0000000000000..8c66626a40cc4
--- /dev/null
+++ b/app/code/Magento/GraphQl/Model/Cors/Validator/RequestValidatorInterface.php
@@ -0,0 +1,20 @@
+300
+
+
+ Access-Control-Max-Age
+
+
+
+
+ Access-Control-Allow-Credentials
+
+
+
+
+ Access-Control-Allow-Headers
+
+
+
+
+ Access-Control-Allow-Methods
+
+
+
+
+ Access-Control-Allow-Origin
+
+
diff --git a/app/code/Magento/GraphQl/etc/graphql/di.xml b/app/code/Magento/GraphQl/etc/graphql/di.xml
index 77fce336374dd..31c798b0505fd 100644
--- a/app/code/Magento/GraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/GraphQl/etc/graphql/di.xml
@@ -30,4 +30,19 @@
+
+
+
+
+
+ - Magento\GraphQl\Controller\Cors\HttpResponseHeaderProvider\AllowHeadersHeaderProvider
+ - Magento\GraphQl\Controller\Cors\HttpResponseHeaderProvider\AllowOriginHeaderProvider
+ - Magento\GraphQl\Controller\Cors\HttpResponseHeaderProvider\AllowMethodsHeaderProvider
+ - Magento\GraphQl\Controller\Cors\HttpResponseHeaderProvider\MaxAgeHeaderProvider
+ - Magento\GraphQl\Controller\Cors\HttpResponseHeaderProvider\AllowCredentialsHeaderProvider
+
+
+
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/CorsGraphQlTest.php b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/CorsGraphQlTest.php
new file mode 100755
index 0000000000000..96e550d4881c9
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/CorsGraphQlTest.php
@@ -0,0 +1,141 @@
+addHeaders([
+ 'Origin' => $origin,
+ 'Content-Type' => 'application/json'
+ ]);
+ return $httpHeaders;
+ }
+
+ /**
+ * Makes the GraphQl request
+ *
+ * @param string $origin
+ * @param string $method
+ * @return void
+ */
+ private function addOriginAndSendGraphQlRequest(string $origin, string $method = 'POST'): void
+ {
+ $this->getRequest()->setMethod($method)
+ ->setHeaders($this->getHeadersForGraphQlRequest($origin))
+ ->setContent('{"query": "{categoryList{name, id }}"}');
+
+ $this->dispatch('/graphql');
+ }
+
+ /**
+ * @magentoConfigFixture default/web/graphql/cors_allowed_origins https://www.example.com
+ * @magentoConfigFixture default/web/graphql/cors_allowed_headers Content-Type
+ * @magentoConfigFixture default/web/graphql/cors_allowed_methods GET,POST,OPTIONS
+ * @magentoConfigFixture default/web/graphql/cors_max_age 86400
+ * @magentoConfigFixture default/web/graphql/cors_allow_credentials 1
+ */
+ public function testIsCorsHeadersPresentInGraphQlResponse()
+ {
+ $this->addOriginAndSendGraphQlRequest('https://www.example.com');
+ $response = $this->getResponse();
+
+ $this->assertNotFalse($response->getHeader('Access-Control-Allow-Origin'));
+ $this->assertNotFalse($response->getHeader('Access-Control-Allow-Headers'));
+ $this->assertNotFalse($response->getHeader('Access-Control-Allow-Methods'));
+ $this->assertNotFalse($response->getHeader('Access-Control-Max-Age'));
+
+ $result = json_decode($response->getContent(), true);
+
+ $this->assertArrayNotHasKey("error", $result);
+ $this->assertEquals("Default Category", $result['data']['categoryList'][0]['name']);
+ $this->assertEquals(2, $result['data']['categoryList'][0]['id']);
+ }
+
+ /**
+ * @magentoConfigFixture default/web/graphql/cors_allowed_origins https://www.example.com
+ * @magentoConfigFixture default/web/graphql/cors_allowed_headers Content-Type
+ * @magentoConfigFixture default/web/graphql/cors_allowed_methods GET,POST,OPTIONS
+ * @magentoConfigFixture default/web/graphql/cors_max_age 86400
+ * @magentoConfigFixture default/web/graphql/cors_allow_credentials 1
+ */
+ public function testNormalRequestDoesNotContainsCorsHeaders()
+ {
+ $httpHeaders = new Headers();
+ $httpHeaders->addHeaderLine('Origin: https://www.example.com');
+ $this->dispatch('/');
+
+ $response = $this->getResponse();
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Origin'));
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Headers'));
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Methods'));
+ $this->assertFalse($response->getHeader('Access-Control-Max-Age'));
+ }
+
+ /**
+ * @magentoConfigFixture default/web/graphql/cors_allowed_origins https://www.example.com
+ * @magentoConfigFixture default/web/graphql/cors_allowed_headers Content-Type
+ * @magentoConfigFixture default/web/graphql/cors_allowed_methods GET,POST,OPTIONS
+ * @magentoConfigFixture default/web/graphql/cors_max_age 86400
+ * @magentoConfigFixture default/web/graphql/cors_allow_credentials 1
+ */
+ public function testCorsNotAddedIfOriginIsNotAllowed()
+ {
+ $this->addOriginAndSendGraphQlRequest('https://www.test.com');
+ $response = $this->getResponse();
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Origin'));
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Headers'));
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Methods'));
+ $this->assertFalse($response->getHeader('Access-Control-Max-Age'));
+ }
+
+ public function testCorsRequestFailsIfCorsConfigurationIsNotProvided()
+ {
+ $this->addOriginAndSendGraphQlRequest('https://www.example.com');
+ $response = $this->getResponse();
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Origin'));
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Headers'));
+ $this->assertFalse($response->getHeader('Access-Control-Allow-Methods'));
+ $this->assertFalse($response->getHeader('Access-Control-Max-Age'));
+ }
+
+ /**
+ * @magentoConfigFixture default/web/graphql/cors_allowed_origins https://www.example.com
+ * @magentoConfigFixture default/web/graphql/cors_allowed_headers Content-Type
+ * @magentoConfigFixture default/web/graphql/cors_allowed_methods GET,POST,OPTIONS
+ * @magentoConfigFixture default/web/graphql/cors_max_age 86400
+ * @magentoConfigFixture default/web/graphql/cors_allow_credentials 1
+ */
+ public function testIsCorsHeadersPresentInGraphQlOptionsResponse()
+ {
+ $this->addOriginAndSendGraphQlRequest('https://www.example.com', 'OPTIONS');
+
+ $response = $this->getResponse();
+ $this->assertNotFalse($response->getHeader('Access-Control-Allow-Origin'));
+ $this->assertNotFalse($response->getHeader('Access-Control-Allow-Headers'));
+ $this->assertNotFalse($response->getHeader('Access-Control-Allow-Methods'));
+ $this->assertNotFalse($response->getHeader('Access-Control-Max-Age'));
+
+ $result = json_decode($response->getBody(), true);
+ $this->assertArrayNotHasKey("error", $result);
+ }
+}