Understand Cross-Origin Resource Sharing (CORS)
Adobe Experience Manager’s Cross-Origin Resource Sharing (CORS) facilitates non-AEM web properties to make client-side calls to AEM, both authenticated and unauthenticated, to fetch content or directly interact with AEM.
The OSGI configuration outlined in this document is sufficient for:
- Single-origin resource sharing on AEM Publish
- CORS access to AEM Author
If multi-origin CORS access is required on AEM Publish, refer to this documentation.
Adobe Granite Cross-Origin Resource Sharing Policy OSGi configuration
CORS configurations are managed as OSGi configuration factories in AEM, with each policy being represented as one instance of the factory.
http://<host>:<port>/system/console/configMgr > Adobe Granite Cross Origin Resource Sharing Policy
Adobe Granite Cross-Origin Resource Sharing Policy (com.adobe.granite.cors.impl.CORSPolicyImpl
)
Policy selection
A policy is selected by comparing the
Allowed Origin
with theOrigin
request header- and
Allowed Paths
with the request path.
The first policy matching these values are used. If none is found, any CORS request is denied.
If no policy is configured at all, CORS requests will also not be answered as the handler is disabled and thus effectively denied - as long as no other module of the server responds to CORS.
Policy properties
Allowed Origins
"alloworigin" <origin> | *
- List of
origin
parameters specifying URIs that may access the resource. For requests without credentials, the server may specify * as a wildcard, thereby allowing any origin to access the resource. It is absolutely not recommended to useAllow-Origin: *
in production since it allows every foreign (i.e. attacker) website to make requests that without CORS are strictly prohibited by browsers.
Allowed Origins (Regexp)
"alloworiginregexp" <regexp>
- List of
regexp
regular expressions specifying URIs that may access the resource. Regular expressions can lead to unintended matches if not carefully built, allowing an attacker to use a custom domain name that would also match the policy. It is generally recommended to have separate policies for each specific origin hostname, usingalloworigin
, even if that means repeated configuration of the other policy properties. Different origins tend to have different life-cycles and requirements, thus benefitting from clear separation.
Allowed Paths
"allowedpaths" <regexp>
- List of
regexp
regular expressions specifying resource paths for which the policy applies.
Exposed Headers
"exposedheaders" <header>
- List of header parameters indicating response headers that browsers are allowed to access. For CORS requests (not pre-flight), if not empty these values are copied into the
Access-Control-Expose-Headers
response header. The values in the list (header names) are then made accessible to the browser; without it, those headers are not readable by the browser.
Maximum Age
"maxage" <seconds>
- A
seconds
parameter indicating how long the results of a pre-flight request can be cached.
Supported Headers
"supportedheaders" <header>
- List of
header
parameters indicating which HTTP request headers can be used when making the actual request.
Allowed Methods
"supportedmethods"
- List of method parameters indicating which HTTP methods can be used when making the actual request.
Supports Credentials
"supportscredentials" <boolean>
- A
boolean
indicating whether or not the response to the request can be exposed to the browser. When used as part of a response to a pre-flight request, this indicates whether or not the actual request can be made using credentials.
Example configurations
Site 1 is a basic, anonymously accessible, read-only scenario where content is consumed via GET requests:
{
"supportscredentials":false,
"exposedheaders":[
""
],
"supportedmethods":[
"GET",
"HEAD"
],
"alloworigin":[
"http://127.0.0.1:3000",
"https://site1.com"
],
"maxage:Integer": 1800,
"alloworiginregexp":[
"http://localhost:.*"
"https://.*\.site1\.com"
],
"allowedpaths":[
"/content/_cq_graphql/site1/endpoint.json",
"/graphql/execute.json.*",
"/content/site1/.*"
],
"supportedheaders":[
"Origin",
"Accept",
"X-Requested-With",
"Content-Type",
"Access-Control-Request-Method",
"Access-Control-Request-Headers",
]
}
Site 2 is more complex and requires authorized and mutating (POST, PUT, DELETE) requests:
{
"supportscredentials":true,
"exposedheaders":[
""
],
"supportedmethods":[
"GET",
"HEAD"
"POST",
"DELETE",
"PUT"
],
"alloworigin":[
"http://127.0.0.1:3000",
"https://site2.com"
],
"maxage:Integer": 1800,
"alloworiginregexp":[
"http://localhost:.*"
"https://.*\.site2\.com"
],
"allowedpaths":[
"/content/site2/.*",
"/libs/granite/csrf/token.json",
],
"supportedheaders":[
"Origin",
"Accept",
"X-Requested-With",
"Content-Type",
"Access-Control-Request-Method",
"Access-Control-Request-Headers",
"Authorization",
"CSRF-Token"
]
}
Dispatcher caching concerns and configuration dispatcher-caching-concerns-and-configuration
Starting with Dispatcher 4.1.1+ response headers can be cached. This makes it possible to cache CORS headers along w the CORS-requested resources, as long as the request is anonymous.
Generally, the same considerations for caching content at Dispatcher can be applied to caching CORS response headers at dispatcher. The following table defines when CORS headers (and thus CORS requests) can be cached.
Allowing CORS request headers
To allow the required HTTP request headers to passthrough to AEM for processing, they must be allowed in the Disaptcher’s /clientheaders
configuration.
/clientheaders {
...
"Origin"
"Access-Control-Request-Method"
"Access-Control-Request-Headers"
}
Caching CORS resposne headers
To allow the caching and serving of CORS headers on cached content, add following /cache /headers configuration to the AEM Publish dispatcher.any
file.
/publishfarm {
...
/cache {
...
# CORS HTTP response headers
# https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers
/headers {
...
"Access-Control-Allow-Origin"
"Access-Control-Expose-Headers"
"Access-Control-Max-Age"
"Access-Control-Allow-Credentials"
"Access-Control-Allow-Methods"
"Access-Control-Allow-Headers"
}
...
}
...
}
Remember to restart the web server application after making changes to the dispatcher.any
file.
It is likely clearing the cache entirely is required to ensure the headers are appropriately cached on the next request after a /cache/headers
configuration update.
Troubleshooting CORS
Logging is available under com.adobe.granite.cors
:
- enable
DEBUG
to see details about why a CORS request was denied - enable
TRACE
to see details about all requests going through the CORS handler
Tips:
-
Manually recreate XHR requests using curl, but make sure to copy all headers and details, as each one can make a difference; some browser consoles allow to copy the curl command
-
Verify if request was denied by the CORS handler and not by the authentication, CSRF token filter, dispatcher filters, or other security layers
- If CORS handler responds with 200, but
Access-Control-Allow-Origin
header is absent on the response, review the logs for denials under DEBUG incom.adobe.granite.cors
- If CORS handler responds with 200, but
-
If dispatcher caching of CORS requests is enabled
- Ensure the
/cache/headers
configuration is applied todispatcher.any
and the web server is successfully restarted - Ensure the cache was properly cleared after any OSGi or dispatcher.any configuration changes.
- Ensure the
-
if required, check presence of authentication credentials on the request.