0

Recently, I wanted to get data from a third-party API using Axios. I've got a CORS error. So I decided to use fetch() instead.

Here is the code. Function that uses fetch():

// function that uses fetch()
export default async function () {
    let ipData = await fetch(
        'https://api.ipdata.co/?api-key=<key>'
    );
    let parsedIpData = await ipData.json();
    console.log(parsedIpData);
    if (parsedIpData && parsedIpData.languages) {
        parsedIpData.languages.forEach((lang) => {
            if (lang.code == 'en' || lang.code == 'ru') {
                console.log('lang.code - ' + lang.code);
                return lang.code;
            } else {
                console.log('fallback locale - en');
                return 'en';
            }
        })
    } else {
        console.log('no response. fallback locale - en');
        return 'en';
    }
}

Function that uses Axios:

// function that uses Axios
export default async function () {
    axios.get(
        'https://api.ipdata.co/?api-key=<key>'
    ).then((response) => {
        if (response && response.languages) {
            response.languages.forEach((lang) => {
                if (lang.code == 'en' || lang.code == 'ru') {
                    console.log('lang.code - ' + lang.code);
                    return lang.code;
                } else {
                    console.log('fallback locale - en');
                    return 'en';
                }
            })
        } else {
            console.log('no response. fallback locale - en');
            return 'en';
        }
    }).catch((error) => {
        console.log(error);
    })
}

And here is my error. I've tried to pass headers to axios.get() already, but the server didn't allow them. enter image description here

  • 5
    Erm, both axios *and* `fetch` obey CORS restrictions. Anything that runs in the browser does. It's a browser-enforced restriction, not just an artefact of whatever you use to do the requests. CORS would be quite useless if the built-in function to make requests didn't have to obey it. – VLAZ Feb 06 '23 at 20:18
  • @VLAZ, you're right, but I didn't get any CORS errors when I used `fetch()`. Do you know any reasons why that happened? Thanks for your feedback! –  Feb 06 '23 at 20:24
  • 1
    CORS has nothing to do with the kind of library you use to do a HTTP request. CORS is a browser security mechanism. Try to reproduce this with two code samples, although it should not be possible. – n9iels Feb 06 '23 at 21:26
  • @Angelina — We don't know what is different between your fetch and axios tests. Maybe you didn't try to read the data with one. Maybe you used different URLs to it as a same origin request. – Quentin Feb 07 '23 at 10:36
  • @n9iels, @Quentin, I've just tried to say that I couldn't make a cross-domain request with Axios, and fetch() worked for me. I don't know exactly why. Also, `fetch()` didn't require any additional config to do the job. I'll edit the post soon and show you the pieces of code. –  Feb 07 '23 at 10:39
  • @Angelina — Fetch and Axios both implement CORS (the latter by proxy as it is a wrapper around XHR). They have the same defaults for any CORS related setting and need the same configuration (although the syntax may vary slightly) for anything like `withCredentials`. – Quentin Feb 07 '23 at 10:54
  • @Quentin, I'm just getting IP data. Also, I've just edited the post. –  Feb 07 '23 at 11:05
  • This looks like you have some code, which you didn't include in the question, which tries to set a non-standard `x-requested-with` header on your axios code. Axios doesn't set that by default. (Note that `x-request-with` is a horrible hack designed to tell the server "I want data for Ajax not for a regular page" which `api.ipdata.co` certainly isn't going to do with. HTTP has a standard `Accept` header which can say "I want JSON in the response" which is a much better way to get the same effect (most servers don't do content negotiation though). – Quentin Feb 07 '23 at 11:06
  • The aforementioned code that you didn't include probably interacts with https://github.com/axios/axios#config-defaults – Quentin Feb 07 '23 at 11:08
  • @Quentin, I don't set this header anywhere else, I'm sure. [Here](https://github.com/loglinn05/catalyst) is the repo of the project where I use this function. –  Feb 07 '23 at 11:09
  • @Quentin, I also didn't set config defaults. –  Feb 07 '23 at 11:11
  • https://jsbin.com/tiyugimaji/edit?html,js,output — The code you provided does not generate a preflight request and then fail saying it wasn't allowed to set `x-requested-by` (it does get a CORS error, but that's on the GET request, not the preflight options request, and I assume its because I don't have a real API key). – Quentin Feb 07 '23 at 11:16
  • 1
    Aside: `axios` returns a promise. Wrapping it in a Promise constructor is inefficient and makes things harder to debug. – Quentin Feb 07 '23 at 11:17
  • @Quentin, I can give you the key if you want. Someone recommended me not posting it. Also, thank you for advice! –  Feb 07 '23 at 11:20
  • I don't need the key. My point is that the code you've provided doesn't have the problem you say it does. I have no idea why `x-requested-by` is trying to be included on the request, but it isn't caused by any of the code you've shared. – Quentin Feb 07 '23 at 11:22
  • @Quentin, all the requests I make are [here](https://github.com/loglinn05/catalyst/tree/main/resources/js/store/modules), in Vuex store modules (`auth`, `resetPassword` and `users` modules only). –  Feb 07 '23 at 11:29
  • @Quentin, I forgot to mention that I use interceptors [here](https://github.com/loglinn05/catalyst/blob/main/resources/js/axios/axiosInstance.js). –  Feb 07 '23 at 11:36
  • @Angelina do you get the same error if you remove this interceptor? You can also try adding `Authorization` header to the fetch request to see if it results in the same error – Konrad Feb 07 '23 at 12:09
  • @Konrad, yes, I get the same error even when I remove the interceptor. I've also added `Authorization` header to the fetch request, and it didn't result in an error. –  Feb 07 '23 at 14:41

1 Answers1

1

The answer is updated. Look at the end of it, please.

It turned out that Axios always sent requests with X-Requested-With header, but it wasn't allowed at the server. Look at the screenshot below to make sure.

enter image description here

So I decided to assign null to X-Requested-With header. I'm not sure if this is the right thing to do, so, please, feel free to educate me on this, if you want. Here is how axios.get() looks now:

axios.get(
    'https://api.ipdata.co/?api-key=<key>', {
        headers: {
            'X-Requested-With': null
        }
    }
).then((response) => {
    if (response.data && response.data.languages) {
        console.log(response.data.languages);
        response.data.languages.forEach((lang) => {
            if (lang.code == 'en' || lang.code == 'ru') {
                console.log('lang.code - ' + lang.code);
                return lang.code;
            } else {
                console.log('fallback locale - en');
                return 'en';
            }
        })
    } else {
        console.log(response);
        console.log('no response. fallback locale - en');
        return 'en';
    }
}).catch((error) => {
    console.log(error);
})

And it worked! I'm so glad! Thank you, everyone, for leading me to this answer!

P.S.: This thread helped me to understand the reason Axios and other libraries include X-Requested-With into request headers.

UPDATE:

@Quentin, you were right! Axios default headers were set in resources/js/bootstrap.js. But I didn't create or modify this file.

/**
 * We'll load the axios HTTP library which allows us to easily issue requests
 * to our Laravel back-end. This library automatically handles sending the
 * CSRF token as a header based on the value of the "XSRF" token cookie.
 */

import axios from 'axios';
window.axios = axios;

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

/**
 * Echo exposes an expressive API for subscribing to channels and listening
 * for events that are broadcast by Laravel. Echo and event broadcasting
 * allows your team to easily build robust real-time web applications.
 */

// import Echo from 'laravel-echo';

// import Pusher from 'pusher-js';
// window.Pusher = Pusher;

// window.Echo = new Echo({
//     broadcaster: 'pusher',
//     key: import.meta.env.VITE_PUSHER_APP_KEY,
//     cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
//     wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
//     wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
//     wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
//     forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
//     enabledTransports: ['ws', 'wss'],
// });