Cross-origin resource sharing (CORS)
Adobe Experience Manager as a Cloud Service’s Cross-Origin Resource Sharing (CORS) facilitates non-AEM web properties to make browser-based client-side calls to AEM’s GraphQL APIs, and other AEM Headless resources.
CORS requirement
CORS is required for browser-based connections to AEM GraphQL APIs, when the client connecting to AEM is NOT served from the same origin (also known as host or domain) as AEM.
AEM Author
Enabling CORS on AEM Author service is different from AEM Publish and AEM Preview services. AEM Author service requires an OSGi configuration to be added to the AEM Author service’s run mode folder, and does not use a Dispatcher configuration.
OSGi configuration
The AEM CORS OSGi configuration factory defines the allow criteria for accepting CORS HTTP requests.
The example below defines an OSGi configuration for AEM Author (../config.author/..
) so it is only active on AEM Author service.
The key configuration properties are:
-
alloworigin
and/oralloworiginregexp
specifies the origins the client connecting to AEM web runs on. -
allowedpaths
specifies the URL path patterns allowed from the specified origins.- To support AEM GraphQL persisted queries, add the following pattern:
/graphql/execute.json.*
- To support Experience Fragments, add the following pattern:
/content/experience-fragments/.*
- To support AEM GraphQL persisted queries, add the following pattern:
-
supportedmethods
specifies the allowed HTTP methods for the CORS requests. To support AEM GraphQL persisted queries (and Experience Fragments), addGET
. -
supportedheaders
includes"Authorization"
as requests to AEM Author should be authorized. -
supportscredentials
is set totrue
as request to AEM Author should be authorized.
Learn more about the CORS OSGi configuration.
The following example supports use of AEM GraphQL persisted queries on AEM Author. To use client-defined GraphQL queries, add a GraphQL endpoint URL in allowedpaths
and POST
to supportedmethods
.
/ui.config/src/main/content/jcr_root/apps/wknd-examples/osgiconfig/config.author/com.adobe.granite.cors.impl.CORSPolicyImpl~graphql.cfg.json
{
"alloworigin":[
"https://spa.external.com/"
],
"alloworiginregexp":[
],
"allowedpaths": [
"/graphql/execute.json.*",
"/content/experience-fragments/.*"
],
"supportedheaders": [
"Origin",
"Accept",
"X-Requested-With",
"Content-Type",
"Access-Control-Request-Method",
"Access-Control-Request-Headers",
"Authorization"
],
"supportedmethods":[
"GET",
"HEAD",
"POST"
],
"maxage:Integer": 1800,
"supportscredentials": true,
"exposedheaders":[ "" ]
}
Example OSGi configuration
AEM Publish
Enabling CORS on AEM Publish (and Preview) services are different from the AEM Author service. AEM Publish service requires an AEM Dispatcher configuration to be added to the AEM Publish’s Dispatcher configuration. AEM Publish does not use an OSGi configuration.
When configuring CORS on AEM Publish, ensure:
- The
Origin
HTTP request header cannot be sent to AEM Publish service, by removing theOrigin
header (if previously added) from the AEM Dispatcher project’sclientheaders.any
file. AnyAccess-Control-
headers should be removed from theclientheaders.any
file and Dispatcher manages them, not AEM Publish service. - If you have any CORS OSGi configurations enabled on your AEM Publish service, you have to remove them and migrate their configurations to the Dispatcher vhost configuration outlined below.
Dispatcher configuration
AEM Publish (and Preview) service’s Dispatcher must be configured to support CORS.
Set CORS headers in vhost
-
Open the vhost configuration file for the AEM Publish service, in your Dispatcher configuration project, typically at
dispatcher/src/conf.d/available_vhosts/<example>.vhost
-
Copy the contents of the
<IfDefine ENABLE_CORS>...</IfDefine>
block below, into your enabled vhost configuration file.code language-none h-17 <VirtualHost *:80> ... <IfModule mod_headers.c> ################## Start of CORS configuration ################## SetEnvIfExpr "req_novary('Origin') == ''" CORSType=none CORSProcessing=false SetEnvIfExpr "req_novary('Origin') != ''" CORSType=cors CORSProcessing=true CORSTrusted=false SetEnvIfExpr "req_novary('Access-Control-Request-Method') == '' && %{REQUEST_METHOD} == 'OPTIONS' && req_novary('Origin') != ''" CORSType=invalidpreflight CORSProcessing=false SetEnvIfExpr "req_novary('Access-Control-Request-Method') != '' && %{REQUEST_METHOD} == 'OPTIONS' && req_novary('Origin') != ''" CORSType=preflight CORSProcessing=true CORSTrusted=false SetEnvIfExpr "req_novary('Origin') -strcmatch '%{REQUEST_SCHEME}://%{HTTP_HOST}*'" CORSType=samedomain CORSProcessing=false # For requests that require CORS processing, check if the Origin can be trusted SetEnvIfExpr "%{HTTP_HOST} =~ /(.*)/ " ParsedHost=$1 ################## Adapt regex to match CORS origin(s) for your environment SetEnvIfExpr "env('CORSProcessing') == 'true' && req_novary('Origin') =~ m#(https://.*\.your-domain\.tld(d+?lang=en)?$)#" CORSTrusted=true # Extract the Origin header SetEnvIfNoCase ^Origin$ ^(.*)$ CORSTrustedOrigin=$1 # Flush If already set Header unset Access-Control-Allow-Origin Header unset Access-Control-Allow-Credentials # Trusted Header always set Access-Control-Allow-Credentials "true" "expr=reqenv('CORSTrusted') == 'true'" Header always set Access-Control-Allow-Origin "%{CORSTrustedOrigin}e" "expr=reqenv('CORSTrusted') == 'true'" Header always set Access-Control-Allow-Methods "GET" "expr=reqenv('CORSTrusted') == 'true'" Header always set Access-Control-Max-Age 1800 "expr=reqenv('CORSTrusted') == 'true'" Header always set Access-Control-Allow-Headers "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers" "expr=reqenv('CORSTrusted') == 'true'" # Non-CORS or Not Trusted Header unset Access-Control-Allow-Credentials "expr=reqenv('CORSProcessing') == 'false' || reqenv('CORSTrusted') == 'false'" Header unset Access-Control-Allow-Origin "expr=reqenv('CORSProcessing') == 'false' || reqenv('CORSTrusted') == 'false'" Header unset Access-Control-Allow-Methods "expr=reqenv('CORSProcessing') == 'false' || reqenv('CORSTrusted') == 'false'" Header unset Access-Control-Max-Age "expr=reqenv('CORSProcessing') == 'false' || reqenv('CORSTrusted') == 'false'" # Always vary on origin, even if its not there. Header merge Vary Origin # CORS - send 204 for CORS requests which are not trusted RewriteCond expr "reqenv('CORSProcessing') == 'true' && reqenv('CORSTrusted') == 'false'" RewriteRule "^(.*)" - [R=204,L] # Remove Origin before sending to AEM Publish if this configuration handles the CORS RequestHeader unset Origin "expr=reqenv('CORSTrusted') == 'true'" ################## End of CORS configuration ################## </IfModule> ... </VirtualHost>
-
Match the desired Origins accessing your AEM Publish service by updating the regular expression in the line below. If multiple origins are required, duplicate this line and update for each origin/origin pattern.
code language-none SetEnvIfExpr "env('CORSProcessing') == 'true' && req_novary('Origin') =~ m#(https://.*.your-domain.tld(:\d+)?$)#" CORSTrusted=true
-
For example, to enable CORS access from origins:
- Any subdomain on
https://example.com
- Any port on
http://localhost
Replace the line with the following two lines:
code language-none SetEnvIfExpr "env('CORSProcessing') == 'true' && req_novary('Origin') =~ m#(https://.*\.example\.com$)#" CORSTrusted=true SetEnvIfExpr "env('CORSProcessing') == 'true' && req_novary('Origin') =~ m#(http://localhost(:\d+)?$)#" CORSTrusted=true
- Any subdomain on
-