Payload gets lost during CORS PUT request - ajax

I have a webpacked client application running on localhost:8080 and an Silex PHP application running with apache on localhost:80
I try to send a PUT request with axios on the client to the silex application with a request payload. But somehow in the silex application I see no payload, the request parameters bag is empty.
This is my vHost CORS config:
Header always set Access-Control-Allow-Origin "http://localhost:8080"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Max-Age "1000"
Header always set Access-Control-Allow-Headers "x-requested-with, x-requested-by, Content-Type, origin, authorization, accept, client-security-token"
# Added a rewrite to respond with a 200 SUCCESS on every OPTIONS request.
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
And these are the request and response headers.
I really don't know what is missing here, I never had any problems with this.

I finally found the answer. Turns out that Silex just does not support JSON formatted content on a request.

Related

Set-Cookies response header cookies not correctly and immediately set on VPS

I have a Spring Boot app and some static files made with Vue on my Virtual Private Server using Apache Web Server.
I have a problem that the cookies from the backend are not set, more specifically, they don't show up in the developers tools.
Locally everything works fine, and it works in the following way:
land on landing page /login
fill in credentials and call /api/login (backend)
in response headers I see 'Set-Cookie: XSRF-TOKEN=ed5479d8-75e0-4a48-affd-e02ded1c17e0; Path=/' and multiple other response headers
cookies show up in developer tools
access arbitrary other endpoint (e.g. /create-pools)
see that XSRF-TOKEN cookie is sent back to the backend
see that the X-XSRF-TOKEN cookie as header is also sent to the backend resulting in a successful call
On my VPS the flow should be the same, but instead:
land on landing page /login
fill in credentials and call /api/login (backend)
in response headers I see 'Set-Cookie: XSRF-TOKEN=ed5479d8-75e0-4a48-affd-e02ded1c17e0; Path=/' and multiple other response headers
cookies DO NOT show up in developer tools
access arbitrary other endpoint (e.g. /create-pools)
see that XSRF-TOKEN cookie is sent back to the backend
X-XSRF-TOKEN cookie is not sent back to the backend resulting in an unsuccessful call
So somehow on my VPS it doesn't use or set the cookies.
Worth mentioning is that in the apache configuration I have:
SSLEngine On
SSLCertificateFile /etc/ssl/certs/SSL_certificate_www_test_com.crt
SSLCertificateKeyFile /etc/ssl/certs/www.test.com.key
SSLCACertificateFile /etc/ssl/certs/www.test.com.cer
ServerAdmin webmaster#localhost
DocumentRoot /home/bart/test/static/
ServerName www.test.com
ServerAlias test.com
<Directory /home/bart/test/static>
AllowOverride All
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.html [L]
</IfModule>
</Directory>
CustomLog ${APACHE_LOG_DIR}/access-test-com.log combined
ProxyPreserveHost On
ProxyRequests Off
ProxyPass /api http://localhost:8082
ProxyPassReverse /api http://localhost:8082
What I have tried so far, but didn't work:
look into withCredentials properties on XHR call (I have it added, but setting it true by default doesn't change anything)
Manually read XSRF-TOKEN cookie to see if its present. It printed undefined, but it strangely is sent to the backend as:
Cookie: XSRF-TOKEN=ed5479d8-75e0-4a48-affd-e02ded1c17e0; in the request headers (so somehow there should be a cookie)
set domain name of cookie in Spring to test.com (so that it doesn't set cookies to localhost)
set path name of cookie in Spring to /
In proxypass I send /api calls to the backend. The strange thing is that once I manually navigate to test.com/api suddenly the cookies showup (with path /). If I, after that, navigate to another page then suddenly the cookies are there. It seems like I first manually have to access a /api endpoint (which is not a page but an HTTP to the backend) before the cookies show up.
How do I solve this for me unknown problem?
Regards,
Bart
In the end it seems to be resolved by changing the way I call the backend. Previously, I called it using https://test.com/api, but I saw that request that used https://www.test.com/api got the cookies set correctly. Whereas the https://test.com/api calls did not. Therefore, I changed the way I call the backend by adding www.
I have no idea why it works this way, but it seems to have solved the problem. If anyone has any idea as to why, please feel free to add a comment. For now I will approve this as the answer.
Thanks all for considering my question.
Bart

Apache rewrite rule based on a host

I have the following configuration, basically its a reverse proxy in front of grafana. I forward the /logout request to the auth server with redirect url and the /login url to /login/generic_oauth.
I want to redirect the /logout to auth server only when the request is from grafana itself that is only if the grafana ui makes the request.
If the request is made by any other origin it should go to the reverse proxy and back. Basically when I logout the user from my angular app I to logout the user my grafana app too. But as I have chained the /logout to the auth server redirect I get a cors error when I make the request.
RewriteEngine on
RewriteRule ^/logout - [C]
I want to excute this rule only if the request is from http://grafana-ip:3001
RewriteRule . "https://auth-server-ip:31443/auth/realms/MDC/protocol/openid-connect/logout?redirect_uri=http://auth-server-ip:8081/login" [NE,R=302,CO=grafana_sess:INVALID:;]
RewriteRule ^/login$ /login/generic_oauth [L,R=302]
ProxyPass / http://grafana-ip:3001/
ProxyPassReverse / http://grafana-ip:3001/
RequestHeader set X-Forwarded-Proto "https"
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Max-Age "1000"
Header always set Access-Control-Allow-Headers "*"
Is there any way I can execute the rule as it is when request is from grafana-ip but not when it is form any other origin
Since you proxy / to the backend, and your clients only request URL's on your proxy w/ referers referencing your proxy, you really have no reason to make the /logout handling conditional. Which is good because you have no ability to discriminate on anything for the same reasons.

Angular2 Headers

When I call server without headers its working and server returns json:
this.http.get(url)
but when I add header:
var headers = new Headers({'x-id': '1'});
this.http.get(url, {'headers': headers})
browser returns error:
XMLHttpRequest cannot load http://domain/api/v1/. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
I also tried add Origin header - browser error: Refused to set unsafe header "Origin"
And Access-Control-Allow-Origin header - no effect
On server (Laravel) I created middleware Cors.php:
<?php
namespace App\Http\Middleware;
use Closure;
class Cors {
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', 'http://localhost:3000')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'x-id');
}
}
Im new to angular2 and CORS requests, dont know what to do.
Angular: 2.0.0-beta.0
Laravel: 5.0
Browser: Google Chrome
A preflighted request with CORS means that an OPTIONS HTTP request is executed before the actual one. You switch from a simple request to the one since you add a custom header in the case of a GET method. This link could help you to understand what happens: http://restlet.com/blog/2015/12/15/understanding-and-using-cors/.
FYI the Origin header is automatically added by the browser when executing a cross domain request.
I think your problem is within the Access-Control-Allow-Origin header. You must set the host that makes the call and not the address of the server. You should have this instead (if your Angular2 application is running on localhost:8080):
return $next($request)
->header('Access-Control-Allow-Origin', 'http://localhost:8080')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'x-id');
Hope it helps you,
Thierry
I had the same issue in my Angular2 application.
The problem, as already stated, is that before every request made by the client a preflight request is sent to the server.
This kind of request have a type OPTIONS, and it's duty of the server to send back a preflight response with status 200 and headers set for accepting requests from that client.
This is my solution (with express):
// Domain you wish to allow
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'YOUR-CUSTOM-HEADERS-HERE');
// Set to true if you need the website to include cookies in requests
res.setHeader('Access-Control-Allow-Credentials', true);
// Check if Preflight Request
if (req.method === 'OPTIONS') {
res.status(200);
res.end();
}
else {
// Pass to next layer of middleware
next();
}
In this way, the client will be authorized and you will be able to set your custom headers in all the requests. In your case, you'll have to set x-id in the Access-Control-Allow-Headers option.
In your case the server has to respond to the preflight request with following headers:
Access-Control-Allow-Origin: *
X-Custom-HeaderAccess-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: x-id
Note that for the Access-Control-Allow-Origin HTTP header it's best practice to set the domain where the angular2 app is hosted explicitly instead of the * which is only necessary for public API's where you don't control all consumers!
I highly recommend you to read following article about CORS:
http://www.html5rocks.com/en/tutorials/cors/
I have been developing an angular2 app with a c# web api back end and ran into an issue with the cors settings as well.
The real problem turned out not to be the cors settings, rather the data that was being sent to the api (js date object to c# datetime type didn't line up correctly). However in your case, is it absolutely essential to pass the x-id in the header rather than as a parameter on the request? For example you could do somthing like this:
getYourDataType(id: number): Observable<YourDataType> {
return this.http.get(url + '/' + id.toString())
.map((response: Response) => <YourDataType>response.json());
From my research after dealing with this issue it seems like angular would try to find the endpoint you were trying to hit, fail to find it and then assume that the reason must be something wrong with the cors settings, rather than the client side data.
You said that the api returns data when you don't set any headers on your request, so if changing from a header to a parameter doesn't work, you could try inspecting the request with a tool like fiddler or the network tab of chrome's dev tools and see if angular constructed each request as you expect it should have. Or comparing the results by manually constructing a request in fiddler, it could be very illuminating as to what exactly is happening to give you this unexpected response.
At any rate figuring out that there is an issue with your data, rather than the cors settings is quite difficult. Angular has you looking at the totally wrong thing trying to figure out a very simple problem. I hope this helps somebody.
the problem is that you also need to set the allowed headers. That's why it's not working. To make it work with a simple angular2 http request, you need to add the following headers:
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept"); // In your case also add x-id or what you are also using.
header('Access-Control-Allow-Methods: POST, PUT, DELETE, GET, OPTIONS');
Try following code, This should work
let headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
headers.append('x-id','1');
this.http.get(url, {'headers': headers}).subscribe(
response => {...},
error => {...}
);

HAProxy CORS OPTIONS header intercept setup

With my NGinx setup I was able to intercept OPTIONS requests from ajax preflight and respond with the correct CORS headers and a 200 response so the request could continue onwards. I am attempting to consolidate my frontend proxies into HAProxy and am having some issues getting this piece of the puzzle working.
My particular issue is that while I am able to add the proper CORS options when there is a server able to respond properly to an OPTIONS request, a few of the backends cannot handle/respond with a 405 error when the preflight request is issued. My haproxy.cfg included the following lines for adding the headers:
capture request header origin len 128
http-response add-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Credentials:\ true if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Headers:\ Origin,\ X-Requested-With,\ Content-Type,\ Origin,\ User-Agent,\ If-Modified-Since,\ Cache-Control,\ Accept if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Methods:\ GET,\ POST,\ PUT,\ DELETE,\ OPTIONS if { capture.req.hdr(0) -m found }
rspadd Access-Control-Max-Age:\ 1728000 if { capture.req.hdr(0) -m found }
The solution given in:
How to send a response with HAProxy without passing the request to web servers works when you set all of the correct headers from a client's request, but is not dynamic which is not an ideal solution.
Any help would be appreciated!
Based on the great answer from anine.io I came up with the following solution which allows to define a list of allowed origins and it also adds the missing Acccess-Control-Allow-Origin Header for all HTTP requests. The answer from anine.io only showed the CORS preflight, but didn't consider the normal requests.
In haproxy.cfg load the cors.lua file (adapt the path if necessary) in the global section
global
lua-load /usr/local/etc/haproxy/cors.lua
Add the CORS configuration to your frontend definition
frontend http-in
# CORS configuration
# capture origin HTTP header
capture request header origin len 128
# add Access-Control-Allow-Origin HTTP header to response if origin matches the list of allowed URLs
http-response add-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if !METH_OPTIONS { capture.req.hdr(0) -m reg -f /usr/local/etc/haproxy/cors-origins.lst }
# if a preflight request is made, use CORS preflight backend
http-request use-service lua.cors-response if METH_OPTIONS { capture.req.hdr(0) -m reg -f /usr/local/etc/haproxy/cors-origins.lst }
Create a file called cors.lua and store it under the path you specified above. The file contains the CORS preflight and if there's no good reason, don't restrict Methods or Headers because you would have to include any restrictions regarding methods or headers in the ACLs defined in the CORS configuration in haproxy.conf. Note: Currently Browsers do not support wildcard * for the Access-Control-Allow-Methods header. The cors.lua file should contain the following content
core.register_service("cors-response", "http", function(applet)
applet:set_status(200)
applet:add_header("Content-Length", "0")
applet:add_header("Access-Control-Allow-Origin", applet.headers["origin"][0])
applet:add_header("Access-Control-Allow-Credentials", "true")
applet:add_header("Access-Control-Allow-Headers", "*")
applet:add_header("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS")
applet:add_header("Access-Control-Max-Age", "1728000")
applet:start_response()
end)
Create a file called cors-origins.lst and store it under the path you specified above in the CORS configuration. The file should contain regular expressions (or just simple strings). If the client sends an Origin header, it will be validated against these regular expressions and only if they match, the CORS Preflight from cors.lua will be returned (for HTTP OPTIONS requests) or the Access-Control-Allow-Origin with the value of the origin header of the client request will be added to the response. An example of the content of cors-origins.lst could be
example.com
localhost.*
.*\.mydomain\.com:[8080|8443]
Test the configuration with http://test-cors.org/. For GET requests there should be no CORS Preflight. For requests other than GET, a CORS Preflight request should be done by the client first (e.g. an HTTP OPTIONS call) to check if the intended method, headers and authorization is allowed.
See HTTP access control (CORS) for further details regarding CORS.
You can use Lua, but you need to make sure HAproxy is built with USE_LUA, by checking haproxy -vv.
This is an example configuration, I haven't tried it myself, but it will give you an idea of what you can do:
# haproxy.cfg
global
lua-load cors.lua
frontend foo
...
http-request use-service lua.cors-response if METH_OPTIONS { req.hdr(origin) -m found } { ... }
# cors.lua
core.register_service("cors-response", "http", function(applet)
applet:set_status(200)
applet:add_header("Content-Length", "0")
applet:add_header("Access-Control-Allow-Origin", applet.headers["origin"][0])
applet:add_header("Access-Control-Allow-Credentials", "true")
applet:add_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Origin, User-Agent, If-Modified-Since, Cache-Control, Accept")
applet:add_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
applet:add_header("Access-Control-Max-Age", "1728000")
applet:start_response()
end)
I want to throw my own answer in the ring here. We've gone through quite some pain to arrive at a working setup. The other answers to this question here were very helpful, but didn't yield a fully working configuration for us.
We wanted to allow any origin. If you need whitelisted origins, see the answer from #Florian Feldhaus for a useful regex trick. Instead of using a whitelist, we echo back the location header:
http-request set-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if { capture.req.hdr(0) -m found }
We also needed to explicitly set Access-Control-Allow-Headers and Access-Control-Expose-Headers. Browser support for wildcards in theses headers is not quite there yet.
So here's what our configuration does:
handles preflight requests using this cors.lua script
handles normal request using http-response set-header to add Access-Control-Allow-* headers
adjust tune.maxrewrite to fit our CORS headers (which are > 1 KB)
The steps for 1) and 2) are explained in the other answers here, but step 3) took us a long time to figure out. I have documented the full config and the journey that took us there in this blog post. The post contains links to the gist on github.
To intercept OPTIONS like in my case, I want to add Access-Control-Max-Age header to response. I modify my haproxy.cfg and add this line to the backend block.
backend api
balance roundrobin
http-response set-header Access-Control-Max-Age 600 if METH_OPTIONS
HAProxy version: 2.2.6
Reference Doc: https://cbonte.github.io/haproxy-dconv/2.2/configuration.html#http-response%20set-header

CORS Request Aborted in Firefox, not in Chrome or IE

I am making a cross-domain ajax request with jQuery. This request works fine in Chrome, and even IE. But in Firefox, the preflight OPTIONS is always aborted by Firefox. The destination server of this request is Apache, and has CORS enabled in the httpd-vhosts.conf
Even more frustrating is that the first request made by Firefox fails. Subsequent refreshes with F5 - the ajax call sometimes completes. In Firebug - I don't see the preflight OPTIONS request made. I've spent far too long trying to solve this - any help greatly appreciated.
<VirtualHost *:8443>
ServerName cyrsggisprd01.sgaas.gi-mss.com
SSLVerifyClient none
WSGIScriptAlias /svm_server D:\Apache24\htdocs\svm_server\adapter.wsgi
<Directory D:\Apache24\htdocs\svm_server>
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"
Header always set Access-Control-Max-Age "1000"
Header always set Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token, session"
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule .* / [R=200,L]
Require all granted
</Directory>
SSLEngine On
SSLCertificateFile D:\Apache24\ssl\server.crt
SSLCertificateKeyFile D:\Apache24\ssl\server.key
</VirtualHost>
I realize this is an old question, but I had a very similar issue to this with a different setup involving IIS instead of Apache, but I managed to figure it out. The core issue was CORS, SSL, and untrusted certificates.
In my case, the CORS client (which is a SPA served from its own URL) and server had untrusted certificates, but an exception was set up for the client in Firefox but not the server. This meant Firefox loaded the SPA successfully, but any CORS requests the SPA made to the server were simply aborted during the pre-flight phase with no further explanation. I believe (but I have not confirmed) Edge does a similar thing with the following error: "Network Error 0x80070005, Access is denied".
So, basically, double check to see your servers have valid certificates, and add any exceptions you need to your browsers. All I had to do in Firefox was to manually visit the server URL and add an exception, and that made the problem go away. This simple step is something that is very easy to overlook (especially when setting up a new development environment), and it can be very frustrating to debug as the generated error messages are misleading.

Resources