'Fixing the 'Access-Control-Allow-Origin' header contains multiple values error in NGINX
I'm setting up a Wordpress website (e.g. http://api.example.com) in order to consumer its API from another static, HTML/JS website (e.g. https://test.example.com).
Both website are hosted on an Nginx server, and are each configured with a conf file and function properly on their own. nginx -t yields no error and I have full access to both websites as expected.
Unfortunately I'm facing issues with CORS.
When attempting to read media (images, videos) content from api.example.com, test.example.com yields the following error in the browser console:
Access to XMLHttpRequest at
'https://api.example.com/wp-json/custom-post/v1/some-data/'
from origin 'https://test.example.com' has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header contains multiple values
'https://test.example.com, https://test.example.com',
but only one is allowed.
Furthermore, on Chrome, this error is followed by a CORB error (Cross-Origin Read Blocking (CORB) blocked cross-origin response https://api.example.com/wp-json with MIME type application/json.).
I have noticed when inspecting the request header in the browser, redundant values for these properties:
Access-Control-Allow-Credentials: true, true
Access-Control-Allow-Origin: https://test.example.com, https://test.example.com
Sensing there might be a redundant Access-Control-Allow-Origin somewhere, I have looked for it in the nginx.conf file and all conf files in sites-enabled, to no avail. I've also looked within the source code of the Wordpress application, used plugins included, for an injection of this header. There were none to be found.
Finally, I have tried to remove the one line in api.example.com.conf adding the Access-Control-Allow-Origin header — it yields the pure and simple No 'Access-Control-Allow-Origin' header is present on the requested resource error for media content in the browser console.
Interestingly, it doesn't yield the CORB error for JSON anymore, and test.example.com is able to read textual content from JSON files.
Here's the content of the api.example.com.conf file:
server {
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
root /var/www/example.com/backend;
server_name api.example.com;
access_log /var/log/nginx/unicorn_access.log;
error_log /var/log/nginx/unicorn_error.log;
charset utf-8;
gzip off;
# Set CORS policy
set $cors_origin "";
set $cors_cred "";
set $cors_header "";
set $cors_method "";
if ($http_origin ~ '^https?:\/\/(localhost|test.example\.com)$') {
set $cors_origin $http_origin;
set $cors_cred true;
set $cors_header $http_access_control_request_headers;
set $cors_method $http_access_control_request_method;
}
add_header Access-Control-Allow-Origin $cors_origin;
add_header Access-Control-Allow-Credentials $cors_cred;
add_header Access-Control-Allow-Headers $cors_header;
add_header Access-Control-Allow-Methods $cors_method;
location / {
index index.php index.html;
try_files $uri $uri/ /index.php?$args;
}
client_max_body_size 50m;
# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
# Prevents hidden files (beginning with a period) from being served
location ~ /\. {
access_log off;
log_not_found off;
deny all;
}
# Send 'expires' headers and turn off 404 logging
location ~* ^.+.(xml|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
access_log off;
log_not_found off;
expires max;
}
# Pass all .php files onto a php-fpm or php-cgi server
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_read_timeout 3600s;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 128k;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
fastcgi_index index.php;
}
# Robots
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Restrictions
location ~* /(?:uploads|files)/.*\.php$ {
deny all;
}
}
I expect test.example.com to consume any content from api.example.com, but I can't get to do so fully.
Thanks for your help!
Solution 1:[1]
It was due to the rest_send_cors_headers filter the REST API hooks into rest_pre_serve_request. It sent CORS headers with API requests.
It can be shut down with the following action:
add_action('rest_api_init', function() {
remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
}, 15);
Solution 2:[2]
function custom_rest_api_init() {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function( $value ) {
header( 'Access-Control-Allow-Origin: *' );
header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
header( 'Access-Control-Allow-Credentials: true' );
header( 'Access-Control-Expose-Headers: Link', false );
header( 'Access-Control-Allow-Headers: X-Requested-With' );
// header( 'Vary: Origin', false );
return $value;
} );
}
add_action( 'rest_api_init', 'custom_rest_api_init', 15 );
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Davy |
| Solution 2 | Emin Temiz |
