How can I stop sending a preflight request on a redirect? - ruby-on-rails

I discovered that six years ago, the previous developer commented out this line of code (Ruby, Rails):
#protect_from_forgery
I replaced it with the default:
protect_from_forgery with: :exception
and now I mysteriously get the following error when I try to add items to my cart while logged out:
Access to XMLHttpRequest at 'https://id.foo-staging.com/openid/checklogin?return_to=http%3A%2F%2Flocalhost.foo-staging.com%3A3000%2Fcart%2Fitems' (redirected from 'http://localhost.foo-staging.com:3000/cart/items') from origin 'http://localhost.foo-staging.com:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
I've pinned down that this is happening because of the following lines:
def get_user_identity_from_open_id_server
redirect_to "#{OPEN_ID_PROVIDER_URL}/openid/checklogin?return_to=#{Rack::Utils.escape(request.url)}"
end
def open_id_authentication
#stuff
get_user_identity_from_open_id_server
end
before_filter :open_id_authentication
I understand what causes a preflight request, at a very high level, thanks to the documentation. But I don't think I'm doing any of those things.
* the request method is anything other than GET, HEAD, or POST
* you’ve set custom request headers other than Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, or Width
* the Content-Type request header has a value other than application/x-www-form-urlencoded, multipart/form-data, or text/plain
So my initial question is how do I determine what is triggering the preflight request, and then maybe I can figure out how to prevent it from happening. Is this a situation that I can change on my end, or does something need to change on id.foo-staging.com (which I don't have access to, but could probably ask the right person to fix it for me).
I've been Googling all day, and nothing seems to make any sense to me, especially because I can't pin down precisely what's wrong.
I can solve the issue with this code:
skip_before_filter :open_id_authentication, :only => [:create], :if => :current_user and :anonymous_cart
But I have to assume that this is unsafe, from a security standpoint?
ETA: This is what I see on the Network tab for this request:
General:
Request URL: https://id.foo-staging.com/openid/checklogin?return_to=http%3A%2F%2Flocalhost.foo-staging.com%3A3000%2Fcart%2Fitems
Referrer Policy: no-referrer-when-downgrade
Request Headers:
Provisional headers are shown
Access-Control-Request-Headers: x-requested-with
Access-Control-Request-Method: GET
Origin: http://localhost.foo-staging.com:3000
Referer: http://localhost.foo-staging.com:3000/p/Product0/1?id=1&slug=Product0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36
Query String Parameters:
return_to: http://localhost.foo-staging.com:3000/cart/items
I presume the problem is the x-requested-with request header. But I don't know how to resolve this.
EATA:
Many JavaScript frameworks such as JQuery will automatically send this header along with any AJAX requests. This header cannot be sent cross-domain:
I guess my only option is to figure out how to rewrite this without AJAX?

To avoid the preflight request you have to remove x-requested-with header but the error that you have is because the response location in the preflight call is different from the Origin.
To fix the problem, update your code to use the new URL as reported by the redirect, thereby avoiding the redirect.

Related

Do browsers block POST requests if POST isn’t in the Access-Control-Allow-Methods value of the preflight OPTIONS response?

I think I understand CORS pretty well, but I'm still a bit puzzled about the browser's behavior when it comes to the preflight requests.
Let's say the browser issues this preflight request:
OPTIONS http://myserver.local:7000/api/order/4 HTTP/1.1
Host: myserver.local:7000
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-my-custom-header
Origin: http://localhost:5000
Sec-Fetch-Mode: cors
Referer: http://localhost:5000/
and my API returns:
HTTP/1.1 204 No Content
Date: Wed, 09 Mar 2022 12:52:50 GMT
Server: Kestrel
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT,DELETE
Access-Control-Allow-Origin: http://localhost:5000
Vary: Origin
Note that the server allows methods PUT and DELETE in the response to the preflight request, but not POST, which is the method of the actual CORS request.
Should the browser not block this request due to the mismatch between the actual request's method and the methods listed in the Access-Control-Allow-Methods header? Or is it enough that the server respond with a 20x status code for the browser to accept the preflight and then send the actual request?
My assumptions was that the browser would compare the allow-methods and block the request if the requested method did no match... Am I missing something?
TL;DR
No, the browser doesn't require the server to explicitly allow the POST method, because the latter, as a so-called CORS-safelisted method, gets a free pass.
More details
What the spec says
The answer, as always, lies in the Fetch standard (section 4.8), which specifies how CORS works:
Let methods be the result of extracting header list values given Access-Control-Allow-Methods and response’s header list.
And further down:
If request’s method is not in methods, request’s method is not a CORS-safelisted method, and request’s credentials mode is "include" or methods does not contain *, then return a network error.
(my emphasis)
What is a CORS-safelisted method? The term is defined in section 2.2.1:
A CORS-safelisted method is a method that is GET, HEAD, or POST.
Interpretation
If the method of the CORS request is one of GET, HEAD, or POST, the browser doesn't require the server to explicitly list that method in the Access-Control-Allow-Methods header for CORS preflight to succeed.
Experiment
I've found Jake Archibald's CORS playground useful for testing my (mis)understanding of CORS. Running this particular instance in your browser may convince you that the POST method doesn't need to be explicitly allowed for CORS preflight to succeed.

Grails CORS not enabled because no origin

I have a grails 2.2.4 application. I wanted to enable CORS
So I installed cors plugin by having the following line in build config.
plugins {
runtime ':cors:1.1.8'
}
Then in the config.groovy
cors.headers = ['Access-Control-Allow-Origin': '*']
But after this when I run the application, CORS in not enabled. So I debugged the CORS plugin. The issue seems to be in CorsFilter class in the following method
private boolean checkOrigin(HttpServletRequest req, HttpServletResponse resp) {
String origin = req.getHeader("Origin");
if (origin == null) {
//no origin; per W3C spec, terminate further processing for both preflight and actual requests
return false;
}
The origin parameter in the above line is always null as the request does not have the parameter 'Origin'. Is there something i'm doing wrong? I'm not looking for the answer which says add a manual header with the name "Origin" since that is not exactly a proper fix
I'm quite new to CORS so appriciate the help.
In addition to Access-Control-Allow-Origin, and in addition to setting the Origin header on request, you probably need to specify these response headers as well:
Access-Control-Allow-Headers: accept
Access-Control-Allow-Headers: origin
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Method: GET
Access-Control-Allow-Method: POST
Also make sure you respond to HTTP OPTIONS requests with these headers and a blank 200 OK response.
For now, let's assume that RestClient is sending the Origin header properly. It may still be getting stripped by your application. You can prevent this using the Access-Control-Allow-Headers: Origin header.
Most of the problems I have had with my web services is that the right headers are being sent, but they are stripped from the message by my web server. So I tend to adopt a shotgun approach of "allow everything" and then one by one remove what I don't need. My allow-headers header usually is pretty long and I end up having to include stuff like Content-Type, X-Requested-With and other junk before my requests will finally go through.
I further recommend that you test using something besides RestClient, if only as a sanity check. I use Postman, a free Chrome app, for all my messaging tests. It looks to me like the problem is with RestClient not sending the proper Origin header.

Server App on Heroku is not accessibe

Hi we have a Ruby on Rails server application on Heroku, but when I send a post request to it, I always get a 400 Bad Request response. I have searched other 400 errors, but none are related to our issue. The HTTP response that we receive looks like this below:
HTTP/1.1 400 Bad Request
Server: Cowboy
Date: Fri, 14 Aug 2015 21:55:25 GMT
Content-Length: 0
The post request that I am sending looks like this below:
POST http://ourapp.herokuapp.com/api/v1/requests HTTP/1.0
Accept-Language: en-us
Accept: text/plain
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Connection: Close
request=600&key=&newKey=danasecretkey&
Sorry, I had to put blank lines after each header or it would all show up on one line.
If I create an HTML form to send the data, there is no issue. It's when I then try to send the same request from our file server, that I get the errors. I tried using a preflight request with all of the correct request headings, but received the same 400 Bad Request error.
Does anyone have any suggestions as to what I might be doing wrong?
Well, just guessing from what you've said:
request=600&key=&newKey=danasecretkey&
It's likely that you have something like params.require(:key) in your controller. And your request is missing that parameter.
Rails will respond with 400 status in case you missed some require'd params.
What fixed it was switching from HTTP1.0 to HTTP1.1, adding the host header and changing the uri.
The logs didn't tell us anything, and the params were ok. The problem was not fully grasping the HTTP header requirements.

What does #_request.env['HTTP_X_MY_TOKEN'] return?

I'm not a ruby or rails programmer, but I'm tasked with reverse engineering an API for an RoR app. My HTTP POST requests are failing a validation check, where this line is supposed to provide a specific piece of data:
value = #_request.env['HTTP_X_MY_TOKEN'];
From what little experience I have and searching I've done, it appears to be looking for an HTTP request header MY_TOKEN but I'm unsure if that's the case.
My current HTTP request looks like this:
POST /myapp HTTP/1.1
Host: website.com:80
Content-Type: application/json
Content-Length: 12
my post data
If that is the case, can I simply add it to my HTTP post request headers as follows:
POST /myapp HTTP/1.1
Host: website.com:80
Content-Type: application/json
MY_TOKEN: sometokentext
Content-Length: 12
my post data
If not, how do I fill this value during my HTTP POST request?
Sending X-MY-TOKEN should do the trick.
As a side note, prepending custom headers with X- is no longer recommended and deprecated according to RFC-6648:
Custom HTTP headers : naming conventions

AFNetworking POST being sent as GET

Please excuse me if this is normal, but I am attempting to send a post request from iOS using AFNetworking. Using Charles to monitor the request, I see that a GET is sent:
GET /api/updateTeamAlert/ HTTP/1.1
Host: www.******r.co
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5
Connection: keep-alive
User-Agent: ****** Alerts/1.0 (iPhone Simulator; iOS 6.1; Scale/2.00)
Is this normal? I am trying to find out why my POST parameters are empty at the server - could this be the cause?
I am creating my request like this:
NSDictionary *params = #{#"device_id":#"test-device", #"innings":#6, #"team":#"WashingtonNationals"};
[_client postPath:#"updateTeamAlert"
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSString *responseStr = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#"Request Successful, response '%#'", responseStr);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"[HTTPClient Error]: %#", error.localizedDescription);
}];
UPDATE
Well, all I had to do to get this working was change the postPath to include the trailing '/' - perhaps this is obvious to most, but I would love an explanation for the accepted answer.
Well, all I had to do to get this working was change the postPath to include the trailing '/' - perhaps this is obvious to most, but I would love an explanation for the accepted answer.
PHP applications often have misconfigured servers that lose information (like HTTP method) when doing a redirect. In your case, adding the / resolved to the canonical path for your particular web service, but in redirecting to that endpoint, the POST was changed into a GET.
Another way to potentially solve this issue is to use AFURLConnectionOperation -setRedirectResponseBlock, and ensure that the redirect request has the correct verb.
#mattt's answer above is incorrect.
HTTP/1.0 302's worked as you'd logically expect "POST /FOO" 302'd to /BAR implied that you'd get "POST /BAR". However, little to no clients implemented it this way, and commonly changed the method to GET. This is somewhat understandable as the new, redirected resource is unknown to the user, and one shouldn't POST willy nilly to an unknown resource.
HTTP/1.1 clears this up - 302's should let the user know about the redirection. 307's make the redirection work as you would expect, maintaining the method.
AFNetworking is setup to act like all the other naughty clients out there - 302's alter the method to GET, giving the client a chance to alert the user. I just had this issue with AFNetworking myself: I set up some breakpoints, stepped through, and watched the method change before my eyes.
I haven't yet tested that 307's worked as defined with AFNetworking, but regardless, 302's are behaving in the odd way that HTTP/1.1 defined them to work.
tl;dr - In HTTP/1.1 use 307's to redirect and maintain the method, not 302's.
I ran into a similar issue where every POST request was being interpreted as a GET request. It turns out, similar to servers messing up when you're missing the trailing slash, the request type can get lost when your DNS redirects www.site.com to site.com or vice versa.
In my case, my DNS forces site.com to redirect to www.site.com, but I had set my base URLs to point to site.com. Thus, when I sent a request to my API at site.com/api, the request was redirected to www.site.com/api and the request type was lost, and the server defaulted to a GET request. So I added the www to my base URL, sent my request directly to www.site.com/api (avoiding the DNS redirect), and my POST requests started working again.
TL;DR: By adding the www (or removing it, depending on your DNS) I eliminated the redirect and fixed the problem.
I facing same problem, I using Laravel for server.
If my request is "http://localhost/api/abc/" then POST method send from client will became GET method in Server.
But if my request is "http://localhost/api/abc" (without "/"), then server will receive POST method.
I founded root cause is because of Redirect rule in .htaccess file:
In my .htaccess have these lines:
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]
These lines will change POST method to GET method if request have "/" in the end.
To fix this issue, I just comment out these lines.
Hope this help.
Another case you will encounter this issue is that when you set up redirection from HTTP to HTTPS automatically, the POST request will be redirected as GET request if this redirection setup is at work. Just updating your API endpoint to HTTPS resolves this case.

Resources