I'm making a weather app with React.js and I want to make a CORS request for fetching data from weather underground website.
What I want is getting a city name, use autocomplete API for finding the city and fetch data for that city.
The problem is, everytime I give a city name (for example: tehran), the xhr.onerror event handler runs and I get this error:
XMLHttpRequest cannot load http://autocomplete.wunderground.com/aq?query=tehran. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
This is my code for fetching data:
var axios = require('axios');
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
xhr.open(method, url, true);
}
else if (typeof XDomainRequest != "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
}
else {
xhr = null;
}
return xhr;
}
function makeCorsRequest(url) {
var autoCompleteText;
var xhr = createCORSRequest('GET', url);
if (!xhr) {
alert('CORS not supported');
return;
}
xhr.onload = function() {
var text = xhr.responseText;
autoCompleteText = text;
}
xhr.onerror = function() {
alert('Woops, there was an error making the request.');
}
xhr.send();
return autoCompleteText;
}
const WEATHER_UNDERGROUND_AUTOCOMPLETE = 'http://autocomplete.wunderground.com/aq?query=';
const WEATHER_UNDERGROUND_URL = 'http://api.wunderground.com/api/eda52d06d32d71e9/conditions/q/';
module.exports = {
getTemp: function(city) {
var encodedCity = encodeURIComponent(city);
var requestAutoComplete = `${WEATHER_UNDERGROUND_AUTOCOMPLETE}${encodedCity}`;
var autoCompleteText = makeCorsRequest(requestAutoComplete);
var foundCity = autoCompleteText.RESULTS[0].name.split(', ');
var requestUrl = `${WEATHER_UNDERGROUND_URL}${foundCity[1]}/${foundcity[0]}.json`;
return axios.get(requestUrl).then(function(res) {
return res.data.current_observation.temp_c;
}, function(err) {
throw new Error(res.data.error);
});
}
}
Screenshot of the app:
localhost:3000/weather page
Because http://autocomplete.wunderground.com/aq?query=tehran doesn’t send the Access-Control-Allow-Origin response header, you must change your frontend code to instead make the request through proxy. Do that by changing the WEATHER_UNDERGROUND_AUTOCOMPLETE value:
const WEATHER_UNDERGROUND_AUTOCOMPLETE =
'https://cors-anywhere.herokuapp.com/http://autocomplete.wunderground.com/aq?query=';
The https://cors-anywhere.herokuapp.com/http://autocomplete.wunderground.com/… URL will cause the request to go to https://cors-anywhere.herokuapp.com, a public CORS proxy which sends the request on to the http://autocomplete.wunderground.com… URL you want.
That proxy gets the response, takes it and adds the Access-Control-Allow-Origin response header to it, and then finally passes that back to your requesting frontend code as the response.
So in the end because the browser sees a response with the Access-Control-Allow-Origin response header, the browser allows your frontend JavaScript code to access the response.
Or use the code from https://github.com/Rob--W/cors-anywhere/ or such to set up your own proxy.
You need a proxy in this case because http://autocomplete.wunderground.com/… itself doesn’t send the Access-Control-Allow-Origin response header—and in that case your browser will not allow your frontend JavaScript code to access a response from that server cross-origin.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS has more details.
Incidentally, you can use curl or some other tool to verify that server isn’t sending the header:
$ curl -i -H 'Origin: http://localhost:3000' \
'http://autocomplete.wunderground.com/aq?query=tehran'
HTTP/1.1 200 OK
Content-type: application/json; charset=utf-8
Content-Length: 2232
Connection: keep-alive
{ "RESULTS": [
{
"name": "Tehran Dasht, Iran",
…
Notice there’s no Access-Control-Allow-Origin in the response headers there.
Here is a simple react component which calls the api with query params and get 's the desired result.
import React, { Component } from 'react'
import axios from 'axios';
export default class App extends Component {
componentDidMount() {
axios.get('http://autocomplete.wunderground.com/aq?query=tehran')
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
})
}
render () {
return (
<div>React simple starter</div>
)
}
}
Are you bound to using axios? if not I would highly recommend Mozilla's Fetch. To make a cors api call with fetch, do this:
var myInit = {
method: 'GET',
mode: 'cors',
credentials: 'include'
};
fetch(YOUR_URL, myInit)
.then(function(response) {
return response.json();
})
.then(function(json) {
console.log(json)
});
You can learn more here: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
If you are facing issues making CORS request, then use this simple chrome extension (Allow control Allow origin).
This will let you make CORS request without adding any extra parameter in headers/config.
Related
I'm building an app with Capacitor JS & Nuxt JS to interface with the Slack API so that I can set my Slack status, I've created a Slack App and have a xoxp- token which works just fine when I hit the endpoint with a POST request via Postman, but from my browser (localhost) and from the running app on my phone I'm getting the following CORS error:
Access to XMLHttpRequest at 'https://slack.com/api/users.profile.set' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.
Now this seems silly because you must use the authorization header to provide the Bearer token for authentication, but even after temporarily omitting this, the CORS error remains.
I'm trying to POST to the endpoint for users.profile.set
View another method
What am I missing in my Axios code?
setSlackStatusWithReminder (title, expiry) {
const body = this.convertToQueryString({
profile: this.convertToQueryString(this.profile),
token: 'xoxp-mytoken'
})
this.$axios.post('https://slack.com/api/users.profile.set', body, {
timeout: 10000,
transformRequest(data, headers) {
delete headers.common['Content-Type'];
return data;
}
}).then(res => {
console.log(res)
if (res.data.ok != true) {
alert('something went wrong with the .then')
}
this.isSettingStatus = false
this.actions.isShown = false
}).catch(err => {
this.isSettingStatus = false
this.actions.isShown = false
})
},
UPDATE
I've got a function to convert my request body into a query string from my data, which looks like:
export default {
data () {
return {
profile: {
status_text: '',
status_emoji: '',
status_expiration: 0
}
}
}
}
Query string function to convert body
convertToQueryString (obj) {
const convert = Object.keys(obj)
.map((key, index) => `${key}=${encodeURIComponent(obj[key])}`)
.join('&')
return convert
},
And I'm building it up like:
const body = this.convertToQueryString({
profile: this.convertToQueryString(this.profile),
token: 'xoxp-mytoken'
})
It's giving me an invalid_profile response.
Slack doesn't respond to the pre-flight OPTIONS request with a compatible response.
Avoid the preflight check entirely by ensuring it matches the requirements to be handled as a so-called "simple request".
Notably, ensure the content-type is application/x-www-form-urlencoded, serialize the request body to match and do not use the Authorization header to pass your bearer token, instead pass it as an argument in your request (token).
Not sure why this was so difficult, the following is a valid POST request to the Slack API:
// this.profile -> is the object with the status_* fields
const body = `profile=${JSON.stringify(this.profile)}&token=some_token`
this.$axios.post('https://slack.com/api/users.profile.set', body, {
timeout: 10000,
transformRequest(data, headers) {
delete headers.common['Content-Type'];
return data;
}
}).then(res => {
console.log(err)
}).catch(err => {
console.log(err)
})
There is a need to write an interceptor for XMLHttpRequest Web API, I have written it to this stage
const { serverUrl, bearerToken } = this.config;
const XMLHttpRequestOpen = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (
method: string,
url: string
) {
if (url.match(new RegExp(`^${serverUrl}`)) !== null && bearerToken) {
this.onreadystatechange = function () {
if (this.readyState === XMLHttpRequest.OPENED) {
this.setRequestHeader(
'Authorization',
`Bearer ${bearerToken}`
);
}
};
}
return XMLHttpRequestOpen.apply(this, arguments);
};
Unfortunately, even though in dev console I see authentication header I still receive 401 server response.
What am I missing? Bearer token is 100% correct, so something is wrong with my implementation.
As it turns out my solution was 100% correct, the problem was with CORS policy on proxy.
I am using javascript to make a POST request, and it works fine when I open my index.html file in the browser and click on the 'POST' button that I have linked to the following code. However, I would like to move this to server side and due to the numerous posts online I am confused as to how to do that? Any help is appreciated.
This is my working js code which returns the values in JSON format
const sendRequest = (method, url, data) => {
const promise = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader(
"accessToken",
"AB 1234"
);
xhr.setRequestHeader("requestId", "req");
xhr.setRequestHeader("deviceId", "dev");
xhr.responseType = "json";
if (data) {
xhr.setRequestHeader("Content-Type", "application/json");
}
xhr.onload = () => {
if (xhr.status >= 400) {
reject(xhr.response);
} else {
resolve(xhr.response);
}
};
xhr.onerror = () => {
reject("Something went wrong!");
};
xhr.send(JSON.stringify(data));
});
return promise;
};
It's not clear how your server connects to your front-end, is it through an API? Is it react? So I'm giving you the most basic answer I could think of, but try to bring more details.
I'm assuming you already have a nodejs server ready to make this request.
The XMLHttpRequest belongs to browser's API and you can'y use it in Node.js, but there are two ways to do it:
1. Use the Node.js HTTP api
2. Use a lib
I think is very important to know how the HTTP api works, but I'm giving you the short answer using a lib called Axios.
const axios = require('axios')
const sendRequest = (method, url, data) => {
axios({
method,
url,
data
})
}
As stated node does not have XHR, but you should be able to re-implement the request without a great deal of effort by using a request
node-fetch resource: https://github.com/node-fetch/node-fetch
Example Request:
const fetch = require('node-fetch');
const body = {a: 1};
fetch('https://httpbin.org/post', {
method: 'post',
body: JSON.stringify(body),
headers: {'Content-Type': 'application/json'}
})
.then(res => res.json())
.then(json => console.log(json));
I'm trying to receive entries from Gravityform Rest API V.2 in Angular by making a get request, except I keep getting 401 http error responses, while my console logs ('Access to XMLHttpRequest at 'livesite' from origin 'localsite' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.'). I have used 'basic authentification' to make the request.
https://docs.gravityforms.com/rest-api-v2/#basic-authentication
https://docs.gravityforms.com/rest-api-v2/#get-forms-form-id-entries
I succesfully used the Postman application to make post/get requests with the same credentials. I have added SSL to my domain and allowed "Access-Control-Allow-Origin: *" in Wordpress.
// Link to form #1
const url = 'https://livesite.com/wp/wp-json/gf/v2/forms/1/entries';
// Base64 key with : and keys are set in environment.ts
const key = btoa(this.publicApiKey + ':' + this.privateApiKey);
// Create HttpHeaders
const headersObject = new HttpHeaders().set('Authorization', 'Basic ' + key);
const httpOptions = { headers: headersObject };
// Get
this.http.get(url, httpOptions)
.subscribe(
response => {
console.log('response', response);
},
error => {
console.log('error', error);
}
);
I have a webapi which is configured to use WINDOWS AUTHENTICATION.
var cors = new EnableCorsAttribute(origen, "*", "*") { SupportsCredentials = true };
config.EnableCors(cors);
In my angular app I have the follwing methods:
GET methods work perfect.
result.CargarAreas = function (callBack, onError) {
//url = WebApi + "Personas";
var url = constants.apiPath + "Areas";
//$http.get(url, { headers: { "Access-Control-Allow-Origin": constants.serverPath } })
$http.get(url, {
withCredentials: true
})
.then(function (data) {
callBack(data);
})
.catch(function (data) {
onError(data);
});
};
POST methods give me this error:
result.GuardarContacto = function (callBack, onError, data) {
//url = WebApi + "Contactos";
var url = constants.apiPath + "Contactos";
$http.post(url, data, { headers: { "Access-Control-Allow-Origin": constants.serverPath } })
.then(function (data) {
callBack(data);
})
.catch(function (data) {
onError(data);
});
};
and finally the web api method
[HttpGet]
[Route("api/AutenticationSite")]
public IHttpActionResult AutenticationSite()
{
string user = HttpContext.Current.Request.LogonUserIdentity.Name.ToString();
string[] subUser = user.Split('\\');
bool respuesta = UsuariosDao.Authorize(subUser[1]);
if (respuesta == true)
{
return Ok("Authenticated: " + user);
}
else
{
return BadRequest("Not authenticated" );
}
}
and the DAMN error we have been fighting for hours:
XMLHttpRequest cannot load
http://a.b.com/api/Contactos. Response to
preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://a.b.com' is
therefore not allowed access. The response had HTTP status code 401.
UPDATE 1
Info about the request and response
Request URL:http://a.b.com/api/Contactos Request Method:OPTIONS Status
Code:200 OK Remote Address:181.143.YY.XX:80 Referrer
Policy:no-referrer-when-downgrade Response Headers (11) Request
Headers view source Accept:/ Accept-Encoding:gzip, deflate, sdch
Accept-Language:es-ES,es;q=0.8
Access-Control-Request-Headers:content-type
Access-Control-Request-Method:POST Cache-Control:no-cache
Connection:keep-alive Host:a.b.com Origin:http://a.b.com
Pragma:no-cache Referer:http://a.b.com/Index.html
User-Agent:Mozilla/5.0 (Windows NT 10.0
Remove access-control header setting in your angular code.
Looks like you have this header getting set at multiple places and thus the output is having none, despite your web api code enabling cors.
Look out for
web.config => httpRuntime => httpHandlers => header getting set
Usage of MVC.CORS NuGet 'instead' of WebAPI.CORS package. Here your need to use the WebAPI one (although it depends on MVC one so don't uninstall it)
No need to change the OPTIONS verb handler in global.asax
Multiple calls to config.EnableCors in different places (global.asax and webapi.config). Search across your source for 'cors' just to be sure on this.
Check if the attribute is set on a global level or controller/action level. It may be that your specific action is getting excluded. Try doing a post on some other controller to be sure
Variable 'origen' correctly stores the client's IP address and port. Any deviation will lead to not sending header. Try using star * rather than specific client to test.