Replicating a browser https request in javascript - javascript

I'm trying to hit an API that needs a cookie to return data.
If i hit the url directly in the browser I get the data i want. The protocol is https.
However, whenever I try to fetch the data using window.fetch I run into CORS errors. I think this is happening because I cant get the cookie in the client request, which is causing the server to redirect to an auth server that is not sending back a CORS header.
I have tried using { credentials: 'include' } to no avail.
I was assuming that because the cookie exists in the browser it will be part of the request.
Any fundamental knowledge I'm missing here?

hmm this is a bit weird, by default fetch uses { credentials: 'same-origin' } which will allow cookies to be sent { credentials: 'include' } also will send cookies even if its not the same origin and that's the only difference.
what i am thinking here is that either somewhere in your code the cookie gets deleted somehow or your request is firing before the cookie is set, or the server doesn't allow CORS or it doesn't allow the OPTIONS method if its a different origin.

Related

How to send session ID cookie in a cross-origin request?

I'm using the Peloton API to generate statistics based on workout data grabbed from their API. Certain API requests require the user to be logged in, which can be done by sending your username and password to /auth/login. It returns a session ID that is needed to get those locked requests, such as workout history. The session ID is seemingly sent as a cookie (using credentials: 'include').
When I set credentials to include, it says "Access to fetch at (url) from origin (my site) has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '\*' when the request's credentials mode is 'include'. How do I fix this? My code is below.
async function loginAndGetData(username, password) {
const info = { 'username_or_email': username, 'password': password };
const response = await fetch("https://pelotoncors.herokuapp.com/https://api.onepeloton.com/auth/login",
{
method: 'POST',
body: JSON.stringify(info)
});
const loginInfo = await response.json();
const workoutInfo = await fetch('https://pelotoncors.herokuapp.com/https://api.onepeloton.com/api/user/' + loginInfo.user_id + '/workouts?limit=1234567890',
{
method: 'GET',
credentials: 'include'
});
}
So I identified multiple problems:
Paleton doesn't have an official API or even an API doc (that doesn't make it impossible to use their API, but it can be difficult, and I would be cautious with that)
CORS
cors-anywhere
Logically, to prevent the use of the API by anybody, Paleton, like anybody else who builds a serious API, decided to put CORS headers on their responses, which is obviously not only from a security standpoint reasonable, but can be quite annoying because it makes it basically impossible to use the API from the browsers because the browser enforces the CORS-headers that are being set on responses by the servers.
So your first idea with cors-anywhere seems rational, but doesn't work because you use credentials: 'include' which requires the 'Access-Control-Allow-Origin' header to NOT be set to wildcard *.
If you are not familiar of what all that above means, let me run you through it:
In your above code, which I assume because of the above error message you run on a browser, you try to make a fetch call to cors-anywhere with the URL of the API and userId. Cors-anywhere makes the call to the API for you and ignores the CORS-Headers (it can do that because it is just a policy created for browsers to increase security), send you the data it gets from the API back BUT with the 'Access-Control-Allow-Origin' (by default) set to wildcard *. Because you use credentials: 'include' though, which requires the 'Access-Control-Allow-Origin' header do NOT be set on wildcard, your browser detects a CORS-policy violation, and you get the above error.
So what can you do now ?
Either you offload the API calls to your own backend (which is BTW always a better option), ignore the CORS by yourself and set the 'Access-Control-Allow-Origin' header appropriately or
Maybe there is a query you can add to the URL for cors-anywhere to change the 'Access-Control-Allow-Origin'? (But I don't know that) or
You look for an alternative
I guess the problem is your backend on Heroku, you need to set header "Access-Control-Allow-Origin": true on your backend.

Ajax CORS Issue: If I use no-cors, the Authorization header disappears [duplicate]

I can hit this endpoint, http://catfacts-api.appspot.com/api/facts?number=99 via Postman and it returns JSON
Additionally I am using create-react-app and would like to avoid setting up any server config.
In my client code I am trying to use fetch to do the same thing, but I get the error:
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:3000' is therefore not allowed
access. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
So I am trying to pass in an object, to my Fetch which will disable CORS, like so:
fetch('http://catfacts-api.appspot.com/api/facts?number=99', { mode: 'no-cors'})
.then(blob => blob.json())
.then(data => {
console.table(data);
return data;
})
.catch(e => {
console.log(e);
return e;
});
Interestingly enough the error I get is actually a syntax error with this function. I am not sure my actual fetch is broken, because when I remove the { mode: 'no-cors' } object, and supply it with a different URL it works just fine.
I have also tried to pass in the object { mode: 'opaque'} , but this returns the original error from above.
I belive all I need to do is disable CORS.. What am I missing?
mode: 'no-cors' won’t magically make things work. In fact it makes things worse, because one effect it has is to tell browsers, “Block my frontend JavaScript code from seeing contents of the response body and headers under all circumstances.” Of course you never want that.
What happens with cross-origin requests from frontend JavaScript is that browsers by default block frontend code from accessing resources cross-origin. If Access-Control-Allow-Origin is in a response, then browsers relax that blocking and allow your code to access the response.
But if a site sends no Access-Control-Allow-Origin in its responses, your frontend code can’t directly access responses from that site. In particular, you can’t fix it by specifying mode: 'no-cors' (in fact that’ll ensure your frontend code can’t access the response contents).
However, one thing that will work: if you send your request through a CORS proxy.
You can also easily deploy your own proxy to Heroku in just 2-3 minutes, with 5 commands:
git clone https://github.com/Rob--W/cors-anywhere.git
cd cors-anywhere/
npm install
heroku create
git push heroku master
After running those commands, you’ll end up with your own CORS Anywhere server running at, for example, https://cryptic-headland-94862.herokuapp.com/.
Prefix your request URL with your proxy URL; for example:
https://cryptic-headland-94862.herokuapp.com/https://example.com
Adding the proxy URL as a prefix causes the request to get made through your proxy, which:
Forwards the request to https://example.com.
Receives the response from https://example.com.
Adds the Access-Control-Allow-Origin header to the response.
Passes that response, with that added header, back to the requesting frontend code.
The browser then allows the frontend code to access the response, because that response with the Access-Control-Allow-Origin response header is what the browser sees.
This works even if the request is one that triggers browsers to do a CORS preflight OPTIONS request, because in that case, the proxy also sends back the Access-Control-Allow-Headers and Access-Control-Allow-Methods headers needed to make the preflight successful.
I can hit this endpoint, http://catfacts-api.appspot.com/api/facts?number=99 via Postman
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS explains why it is that even though you can access the response with Postman, browsers won’t let you access the response cross-origin from frontend JavaScript code running in a web app unless the response includes an Access-Control-Allow-Origin response header.
http://catfacts-api.appspot.com/api/facts?number=99 has no Access-Control-Allow-Origin response header, so there’s no way your frontend code can access the response cross-origin.
Your browser can get the response fine and you can see it in Postman and even in browser devtools—but that doesn’t mean browsers expose it to your code. They won’t, because it has no Access-Control-Allow-Origin response header. So you must instead use a proxy to get it.
The proxy makes the request to that site, gets the response, adds the Access-Control-Allow-Origin response header and any other CORS headers needed, then passes that back to your requesting code. And that response with the Access-Control-Allow-Origin header added is what the browser sees, so the browser lets your frontend code actually access the response.
So I am trying to pass in an object, to my Fetch which will disable CORS
You don’t want to do that. To be clear, when you say you want to “disable CORS” it seems you actually mean you want to disable the same-origin policy. CORS itself is actually a way to do that — CORS is a way to loosen the same-origin policy, not a way to restrict it.
But anyway, it’s true you can—in your local environment—do suff like give a browser runtime flags to disable security and run insecurely, or you can install a browser extension locally to get around the same-origin policy, but all that does is change the situation just for you locally.
No matter what you change locally, anybody else trying to use your app is still going to run into the same-origin policy, and there’s no way you can disable that for other users of your app.
You most likely never want to use mode: 'no-cors' in practice except in a few limited cases, and even then only if you know exactly what you’re doing and what the effects are. That’s because what setting mode: 'no-cors' actually says to the browser is, “Block my frontend JavaScript code from looking into the contents of the response body and headers under all circumstances.” In most cases that’s obviously really not what you want.
As far as the cases when you would want to consider using mode: 'no-cors', see the answer at What limitations apply to opaque responses? for the details. The gist of it is:
In the limited case when you’re using JavaScript to put content from another origin into a <script>, <link rel=stylesheet>, <img>, <video>, <audio>, <object>, <embed>, or <iframe> element (which works because embedding of resources cross-origin is allowed for those)—but for some reason you don’t want to/can’t do that just by having the markup of the document use the resource URL as the href or src attribute for the element.
When the only thing you want to do with a resource is to cache it. As alluded to in What limitations apply to opaque responses?, in practice the scenario that’s for is when you’re using Service Workers, in which case the API that’s relevant is the Cache Storage API.
But even in those limited cases, there are some important gotchas to be aware of; see the answer at What limitations apply to opaque responses? for the details.
I have also tried to pass in the object { mode: 'opaque'}
There is no 'opaque' request mode — opaque is instead just a property of the response, and browsers set that opaque property on responses from requests sent with no-cors mode.
But incidentally the word opaque is a pretty explicit signal about the nature of the response you end up with: “opaque” means you can’t see into any of its details; it blocks you from seeing.
If you are trying to address this issue temporarily on your localhost, you can use this chrome extension : Allow CORS Access-Control-Allow-Origin
https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf
If you are using Express as back-end you just have to install cors and import and use it in app.use(cors());.
If it is not resolved then try switching ports.
It will surely resolve after switching ports
So if you're like me and developing a website on localhost where you're trying to fetch data from Laravel API and use it in your Vue front-end, and you see this problem, here is how I solved it:
In your Laravel project, run command php artisan make:middleware Cors. This will create app/Http/Middleware/Cors.php for you.
Add the following code inside the handles function in Cors.php:
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
In app/Http/kernel.php, add the following entry in $routeMiddleware array:
‘cors’ => \App\Http\Middleware\Cors::class
(There would be other entries in the array like auth, guest etc. Also make sure you're doing this in app/Http/kernel.php because there is another kernel.php too in Laravel)
Add this middleware on route registration for all the routes where you want to allow access, like this:
Route::group(['middleware' => 'cors'], function () {
Route::get('getData', 'v1\MyController#getData');
Route::get('getData2', 'v1\MyController#getData2');
});
In Vue front-end, make sure you call this API in mounted() function and not in data(). Also make sure you use http:// or https:// with the URL in your fetch() call.
Full credits to Pete Houston's blog article.
You can also set up a reverse proxy which adds the CORS headers using a self-hosted CORS Anywhere or Just CORS if you want a managed solution.
https://justcors.com/<id>/<your-requested-resource>
http://cors-anywhere.com/<your-requested-resource>
Very easy solution (2 min to config) is to use local-ssl-proxy package from npm
The usage is straight pretty forward:
1. Install the package:
npm install -g local-ssl-proxy
2. While running your local-server mask it with the local-ssl-proxy --source 9001 --target 9000
P.S: Replace --target 9000 with the -- "number of your port" and --source 9001 with --source "number of your port +1"
Solution for me was to just do it server side
I used the C# WebClient library to get the data (in my case it was image data) and send it back to the client. There's probably something very similar in your chosen server-side language.
//Server side, api controller
[Route("api/ItemImage/GetItemImageFromURL")]
public IActionResult GetItemImageFromURL([FromQuery] string url)
{
ItemImage image = new ItemImage();
using(WebClient client = new WebClient()){
image.Bytes = client.DownloadData(url);
return Ok(image);
}
}
You can tweak it to whatever your own use case is. The main point is client.DownloadData() worked without any CORS errors. Typically CORS issues are only between websites, hence it being okay to make 'cross-site' requests from your server.
Then the React fetch call is as simple as:
//React component
fetch(`api/ItemImage/GetItemImageFromURL?url=${imageURL}`, {
method: 'GET',
})
.then(resp => resp.json() as Promise<ItemImage>)
.then(imgResponse => {
// Do more stuff....
)}
I had a similar problem with my browser debugger saying my response.body was null but fiddler and the developer tools show it as populated that turned out to be basically the same scenario as this. I was using a local Angular application hitting a Web Api service running on IISExpress. I fixed it by following the steps outlined here to find the correct applicationhost.config file to add a Access-Control-Allow-Origin header like so:
<customHeaders>
<clear />
<add name="X-Powered-By" value="ASP.NET" />
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
</customHeaders>
If all the above solutions don't work, probably it's because of the file permissions as sometimes even if you have fixed the non-cors problem using Heroku or another way, it throws 403 forbidden error. Set the directory/file permissions like this:
Permissions and ownership errors
A 403 Forbidden error can also be caused by incorrect ownership or permissions on your web content files and folders.
Permissions
Rule of thumb for correct permissions:
Folders: 755
Static Content: 644
Dynamic Content: 700

cors problem when working with different ports and fetch

I work with node on port 3000 and have a php backend on a different port. I use 'fetch' to send and retrieve data from the php backend on the different port.
I need to send cookies with the fetch request to tell php which session should be used. But I always get a cors error.
I got told that the Header set Access-Control-Allow-Origin in the htaccess file cannot be set to '*' (allow all domains), when 'credentials' are set to 'include', so I entered a complete domain (http://localhost:3000). But then I get this error: Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’ and a link that could help me: https://developer.mozilla.org/de/docs/Web/HTTP/CORS/Errors/CORSMissingAllowCredentials
there stands, when I use the fetch API, I have to set the credentials to 'omit' instead of 'include' but then the fetch api sends no cookies.
I am completely lost and need help. Thanks!
Just set the credentials Headers in your htaccess: Header Set Access-Control-Allow-Credentials: true

Cookie is not set automatically by the browser

I am trying to login by calling an API via a POST HTTP request.
post(
postLogin(email),
JSON.stringify({password: passwd}),
{ headers: { "Content-Type":"application/json" },
credentials: 'include' // i also tried with 'same-origin'
}
)
I am using Redux and React. In the API response, I receive the Set-Cookie header (I can see it in browser dev tools), but for some reason I cannot access it in my code and the browser doesn't set the cookie. I'm using Chrome Version 63.0.3239.84. It is a cross-origin request, so I have the following CORS headers set so I think it is not from here.
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:GET, OPTIONS
Access-Control-Allow-Origin:*
Do you have any suggestions or someone has this issue too? Thank you!
I have finally fixed it. The problem was that my application and the API were not in the same domain. I configure a local NGINX in order to have them under the same domain and it works fine.

Set-Cookie in HTTP header is ignored with AngularJS

I'm working on an application based on AngularJS on client side and Java for my API (Tomcat + Jersey for WS) on server side.
Some path of my API are restricted, if the user doesn't have a session the response status returned is 401. On the client side, 401 http status are intercepted to redirect the user to the login page.
Once the user is authenticated, I create a session on the server side httpRequest.getSession(true);
and the response send to the client does have the Set-cookie instruction in its header :
Set-Cookie:JSESSIONID=XXXXXXXXXXXXXXXXXXXXX; Domain=localhost; Path=/api/; HttpOnly
The problem is that the cookie is never put on the client side. When I inspect cookie for localhost domain it's empty, so the next requests don't have this cookie in their header and client side still couldn't access to the restricted path of my API.
The client and the server are on the same domain but they don't have the same path and the same port number :
Client : http://localhost:8000/app/index.html
Server : http://localhost:8080/api/restricted/
Additional info : CORS is enabled on the both side :
"Access-Control-Allow-Methods", "GET, POST, OPTIONS"
"Access-Control-Allow-Origin", "*"
"Access-Control-Allow-Credentials", true
Any idea for making the Set-cookie works properly ?
Is it an AngularJS related issue ?
I found an issue in AngularJS that help me to move forward.
It seems that "Access-Control-Allow-Credentials" : true was not set on the client side.
Instruction $httpProvider.defaults.withCredentials = true was ignored.
I replace $resource call by a simple $http call with {withCredentials:true} in the config parameter.
I've managed to solve an issue very similar to yours.
My Play! backend tried to set a session Cookie which I could not catch in Angular or store via browser.
Actually the solution involved a bit of this and a bit of that.
Assuming you've solved the initial issue, which can be solved only by adding a specific domain to the Access-Control-Allow-Origin and removing the wildcard, the next steps are:
You have to remove the HTTP-Only from the Set-Cookie header, otherwise you will never be able to receive a cookie "generated" by your angular code
This setup will already work in Firefox, though not in Chrome
To make it work for Chrome too, you need to:
a) send a different domain from localhost in the cookie, using the domain your WS are "hosted". You can even use wildcards like .domain.com instead of ws.domain.com
b) then you'll need to make a call to the domain you specified in the cookie, otherwise Chrome won't store your cookie
[optional] I would remove that /api path in favor of a /
And that should to the trick.
Hope to have been of some help
In your post request on the client side, make sure to add the following:
For jquery ajax requests:
$.ajax({
url: "http://yoururlgoeshere",
type: "post",
data: "somedata",
xhrFields: {
withCredentials: true
}
});
With Angular's $http service :
$http.post("http://yoururlgoeshere", "somedata", {
withCredentials: true
});
You need work on both the server and client side.
Client
Set $http config withCredentials to true in one of the following ways:
Per request
var config = {withCredentials: true};
$http.post(url, config);
For all requests
angular.module("your_module_name").config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push(['$q',
function($q) {
return {
request: function(config) {
config.withCredentials = true;
return config;
}
};
}
]);
}
]);
Server
Set the response header Access-Control-Allow-Credentials to true.
The addition HttpOnly means that the browser should not let plugins and JavaScript see the cookie. This is a recent convention for securer browsing. Should be used for J_SESSIONID but maybe not here.
Just solved a problem like this.
I was doing this and not working...:
$cookies.put('JSESSIONID', response.data);
Cookies are saved in the browser, but when I sent a new request, all the cookies were sent exept mine. (my cookie is JSESSIONID)
then i look in the chrome inspector and i found this:
THE PROBLEM IS THAT WAS NOT THE CORRECT PATH!!!
then I tried this and my cookies were sent. yay! :
$cookies.put('JSESSIONID', response.data, {'path':'/'});
I do not know if this is your case, but this worked for me.
regards!

Categories

Resources