I have pretty basic axios code:
axios.get(GEO_IP)
.then(res => res)
.catch(err => err);
Also, I set next axios defaults:
axios.defaults.headers["content-type"] = "application/json";
axios.defaults.headers.common.authorization = `Bearer ${token}`;
authorization token no needed for this public API. When I try to fire query using axios.get, I see next error in console:
Failed to load https://ipfind.co/me?auth=8964b0f3-4da1-46eb-bcb4-07a9614a6946: 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.
When I rewrite axios using native XMLHttpRequest:
new Promise((resolve, reject) => {
const http = new XMLHttpRequest();
http.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
// result
}
};
http.open("GET", GEO_IP, true);
http.send();
});`
Everything works fine, without any errors. Could someone clarify why axios query causes the error and how I can fix it?
The error message says:
Response to preflight request
This has nothing to do with axios. You are trying to send different requests. When you are using axios, you are setting up the request in such a way that a preflight is required.
axios.defaults.headers["content-type"] = "application/json";
axios.defaults.headers.common.authorization = `Bearer ${token}`;
Either of the above would trigger a preflight.
The XMLHttpRequest version (which doesn't set HTTP headers) doesn't require a preflight, so the server not supporting the preflight isn't a problem.
This means that while your server sends the right CORS headers for the GET request, it isn't for the OPTIONS request that is being sent by axios as part of the preflight request.
Some requests don't require a preflight request. But I'm guessing that axios always sends one.
See MDN for more details on this.
Related
I have an axios request interceptor that adds bearer authorization via an access token. I also have a response interceptor that catches 'token expired' responses, gets a new token via a refresh mechanism, and retries the original request. This seems like it should work, except when the original request is retried, it seems to have lost all its headers. This confuses my API backend as it expects a Content-Type (which was there in the original request).
Note: I'm familiar with the large number of questions about why axios doesn't respect a custom Content-Type header. That's not my problem -- I'm not setting one -- axios does a fine job determining that on its own. I'm just confused at why the Content-Type header that axios sets itself is getting removed when resending the original request.
Relevant code:
const api = axios.create({
baseURL: "https://www.mycoolapi.com",
timeout: 5000,
withCredentials: true,
});
api.interceptors.request.use((config) => {
// Add the auth header (not messing with any other headers)
config.headers!.Authorization = `Bearer ${accessToken}`;
return config;
}, (error) => Promise.reject(error));
api.interceptors.response.use((response) => response,
async (error) => {
if (error.response.status == 401 && refreshToken && !error.config._isRetry) {
// Token expired? Refresh, then retry the original request
error.config._isRetry = true;
await refreshLogIn();
return api(error.config);
}
throw error;
});
async function refreshLogIn() {
// Use the refresh token to get a new token pair
const response = await api.post( "/token",
new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
})
);
accessToken = response.data["access_token"];
refreshToken = response.data["refresh_token"];
}
If I put a breakpoint on return api(error.config) in the response interceptor, and inspect the original request (which is in error.config), I get the following headers (reminder, I added only the Authorization header, the rest are axios defaults or calculated from the requeset):
If I then step through to the request interceptor where the original response is being retried, I get these headers instead:
Content-Type has been removed, along with what was in Symbol(defaults) (whatever that is).
What am I missing here?
As #Phil noted in the comments, this is a bug in axios >= 1.0.0. I suppose one could downgrade to 0.27.2, but I just rewrote using good old fetch. My use case was simple enough anyway, and one less dependency is never a bad thing.
I understand that there are many similar questions, but I am posting this because I feel it is slightly different.
I am trying to send a GET request to the Slack API using an HTTP request.
Specifically, the code looks like the following.
import useSWR from "swr";
const useSlackSearch = (query: string) => {
const token = process.env.NEXT_PUBLIC_SLACK_API_USER_TOKEN;
const myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer " + token);
const slackURL = `https://slack.com/api/search.messages?query=${query}`;
const fetcher = async (url: string) => {
const response = await fetch(url, {
headers: myHeaders,
}).then((res) => res.json());
return response;
};
const { data, error } = useSWR(slackURL, fetcher, {
revalidateOnFocus: true,
revalidateOnReconnect: true,
});
if (error) {
return console.log(`Failed to load: ${error}`);
} else if (!data) {
return console.log("Loading...");
} else {
console.log(data);
return data;
}
};
export default useSlackSearch;
The environments I'm using are as follows.
Device: MacBook Air
OS: macOS
Browser: Chrome
From: localhost:3000
To: Slack API html page (https://slack.com/api/search.messages)
After reading the MDN articles like below, I understood that
There is such a thing as a simple HTTP request as defined by MDN
If the request you want to send does not correspond to this simple request, the browser will send a preflight request
In the response to that preflight request, there is a header called Access-Control-Allow-Headers.
Only headers set to the value of this Access-Control-Allow-Headers header can be used as headers in the main request after preflighting.
In this case, I tried to use the Authorization header, but it was trapped by the above restriction.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
That's all I understand.
However, on the official Slack API page for the method in question, it says to specify the token in the Authorization header, so I'm having trouble.
I also don't understand how to specify the Access-Control-Request-Headers in the preflight header, as described in another questioner's thread. The reason is that the only thing that communicates to the Slack API is the browser in this case, and the only relevant source is JavaScript (React / Next.js to be exact)!
After that, I found preflight response from Slack API as follows;
access-control-allow-headers: slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags
As I thought, I understand that Authorization is not allowed because it is not included as a value. So the question is how to solve it.
Furthermore, I found out later that the preflight request from the browser properly declared that it wanted to use Authorization as an actual request header. However, the preflight response did not contain the value.
Following CBroe's advice, I was able to contact the Slack help center directly, so I asked this problem. What I found out as a result is that HTTP requests from browsers are not supported as of the end of February 2022. Of course, they have received quite a lot of requests regarding this, so they hope to address it at some point.
This time, the browser sent Access-Control-Request-Headers:Authorization in the preflight request. But the Slack API server side did not allow the Authorization header in the request from the browser. Therefore, Authorization was not set in the Access-Control-Allow-Headers in the preflight response from the Slack API side.
As a result, the response from the Slack API side returned Invalid Auth, even though Authorization was added as a header when making an actual request from the browser.
Through this error, I gained a deeper understanding of HTTP requests such as CORS and preflighting, but since it is not explicitly written on the official Slack website, I left it here.
What is Preflight: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
What is Access-Control-Allow-Header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
What is CORS simple request: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
I could not get the Authorization header to work either. However, Slack provided this example for adding token authentication to the Post body following the deprecation of the query parameters method.
This worked for me to make Web API calls to Slack from the browser (for testing) so that Slack would read the token for authentication. Note, according to Slack's best practices for security, user and bot tokens should be stored with care and not used in client-side Javascript:
try {
const res = await fetch("https://slack.com/api/conversations.list", {
method: "POST",
body: `token=${TOKEN}`, // body data type must match "Content-Type" header
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}).catch((error) => {
console.log(error);
});
if (!res.ok) {
throw new Error(`Server error ${res.status}`);
} else {
const data = await res.json();
console.log(data);
}
} catch (error) {
console.log(error);
}
using token in request body instead of Authorization header worked for me.
axios({
method: 'post',
url: 'https://slack.com/api/chat.postMessage',
data: `text=Hi&channel=D048GGYTJUK&token=${process.env.TOKEN}`
})
We’re making a request for an API from one of our online-distributors.
However, we get a CORS-Error.
Access to XMLHttpRequest at 'https://api.cloud.im/marketplace/eu/products' from origin 'http://www.im-cmp.ch' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
We did the request in Postman and it worked. I tried to set the requestHeaders the exact same way they are set in Postman (including the hidden headers), however, there is an Error since the hidden headers can’t be set.
Refused to set unsafe header "Host"
Is this a client or a server problem? Am I maybe missing a requestHeader?
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function() {
if(this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("GET", "https://api.cloud.im/marketplace/eu/products");
xhr.setRequestHeader("X-Subscription-Key", "OUR PERSONAL SUBSCRIPTION KEY");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "OUR BEARER TOKEN");
// xhr.setRequestHeader("Host", "http://www.im-cmp.ch/");
xhr.setRequestHeader("accept", "*/*");
// xhr.setRequestHeader("Accept-Encoding", "gzip, deflate, br");
// xhr.setRequestHeader("Connection", "keep-alive");
xhr.send();
This is definitely a server problem.
The server has to send the Access-Control-Allow-Origin-header: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.
Also, the error states that this happened during a preflight-request, meaning there was a OPTIONS request made beforehand, which would also need the response-header(s) needed for CORS.
The request works in Postman, since CORS is a feature only really relevant in browsers, to protect users.
Edit:
Also it is important that the server allows the request-headers you are sending using the Access-Control-Allow-Headers-header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
In one of my projects I use the JxBrowser in a Netbeans application where my ReactApp is running.
I want to send a post request from the ReactApp and intercept it in my custom Protocol Handler in the JxBrowser.
The request is done via 'superagent':
request
.post('http://my-url')
.send({test: 'it'})
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(callback)
I receive the request in my ProtocolHandler but I do not know how to get the post body out of the request.
urlRequest.getUploadData() //<-- returns null
What is the correct way to get the posts body here?
You're making a cross-origin request. A preflight "OPTIONS" request is sent in this case and you need to handle it properly in your ProtocolHandler. In this particular case you should set the certain headers telling the browser that the requested features are allowed:
if (request.getMethod().equals("OPTIONS")) {
URLResponse urlResponse = new URLResponse();
String origin = request.getRequestHeaders().getHeader("Origin");
HttpHeadersEx headers = urlResponse.getHeaders();
headers.setHeader("Access-Control-Allow-Methods", "POST");
headers.setHeader("Access-Control-Allow-Headers", "Content-Type");
headers.setHeader("Access-Control-Allow-Origin", origin);
urlResponse.setStatus(HttpStatus.OK);
return urlResponse;
}
Also, in order to allow JxBrowser to detect the POST data type properly, you should set the "Content-Type" request header with the corresponding value. In this case it should be the "application/x-www-form-urlencoded".
request
.post('http://my-url')
.send({test: 'it'})
.set('Accept', 'application/json')
.set('Content-Type', 'application/x-www-form-urlencoded')
.end(callback)
Then you'll receive a POST request with your data. I recommend that you take a look at the following article that contains the details related to CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
If you're making a request to the same origin, you can avoid handling cross-origin requests just by setting the proper "Content-Type" header.
I try to get issues from redmine via them Rest Api. When I call it from Postman I get response, but when I do it from my angular App I get such error
OPTIONS https://redmine.ourDomain.net/issues.json 404 (Not Found)
XMLHttpRequest cannot load https://redmine.ourDomain.net/issues.json. 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. The response had HTTP status code 404.
Its how I do it in Angular
login(user: User): Observable<boolean> {
var headers: Headers = new Headers();
headers.append("Authorization", "Basic " + btoa(user.login + ":" + user.password));
let options = new RequestOptions({ headers: headers });
return this.http.get("https://redmine.ourDomain.net/issues.json", options)
.map((response: Response) => {
debugger;
if (response.status == 200) {
// set token property
// store username and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify({ user }));
// return true to indicate successful login
return true;
} else {
// return false to indicate failed login
return false;
}
});
}
And there how request looks in my browser
You'll need to enable CORS access on the backend: http://www.redmine.org/plugins/redmine_cors
Here's a nice extension that will let you test frontend code outside of normal CORS restrictions. It's strictly for testing and won't help a production app, but nice to have: https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi
CORS must be set up in the backend. Please note that is NOT a good practice to allow all origins Access-Control-Allow-Origin: '*' and that you will need to specify the other headers as well:
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: authorization.