Add authentication header via XMLHttpRequest Web API - javascript

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.

Related

Slack API CORS error with axios in Vue JS

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)
})

How to use http request headers in Got?

I have a very simple goal in mind. I want to make an API request from an API known as Zomato from my node.js server application. I'm using an https request framework known as Got, which is supposed to be a lighter version of request API.
var got = require('got');
var httpheaders = {
'Accept': 'application/json',
'user-key': '**********************',
'json': true
}
got('https://developers.zomato.com/api/v2.1/geocode?lat=35&lon=34', {httpheaders}).then(response => {
console.log('We got something');
console.log(response.body);
}).catch(error => {
console.log('We got nothing');
});
When I attempt to run this I catch an error and print, "We got nothing". I don't seem to know how to actually include http request headers, but I can't figure out what the proper syntax would be based off the documentation. Any help would be appreciated. Thanks!
https://github.com/sindresorhus/got/blob/HEAD/documentation/2-options.md
You could use options, like this
import got from 'got';
const options = {
headers: {
foo: 'bar'
}
};
const data = await got(url, options).json();

How to make CORS Request

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.

401 unauthorized except through browser

I've been trying to do an HTML GET request, using a URL that works in the browser but somehow results in a 401 Unauthorized error when I run my code. The problem is that I did provide authentication; the URL is of the form
http://username:password#url?param=value
It succeeds in Firefox and Chrome (I also went through Incognito mode to avoid cookies), as well as Google's Postman app, but despite trying several other HTTP GET request methods they all return unauthorized error. I've run it through REST API and XMLHttpRequest, as well as command line.
Any ideas on what could be causing this? Or, even better, if someone's had a similar problem and has a solution?
(Note: I'm pretty new to this whole thing, not sure if I was clear/detailed enough. I'll do my best to elaborate if anyone needs.)
Edit: Here's some idea of the original code I was running:
var func = function() {
var options = {
baseUrl: SERVER,
uri: '/device.cgi',
qs: {
param1: value1,
param2: value2
}
};
request(options, function(err, response, body) {
if (err) console.log(err);
else console.log(body);
});
};
I also ran
function httpGet(theUrl)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, false ); // false for synchronous request
xmlHttp.send( null );
return xmlHttp.responseText;
}
from this question and got the same Unauthorized result.
And because apparently the device I'm using is fine with POST as well,
var func = function () {
var formData = {
form: {
param1: value1,
param2: value2
}
};
request.post(SERVER + 'device.cgi', formData, function (err, response, body) {
if (err) {
console.log(err);
}
else console.log(body);
}).auth(userID, userPass);
};
still with exactly the same result.
http://username:password#url?param=value
The browser is taking that URL, and creates the Basic HTTP Authorization header for you.
Node.js does not do that for you, and thus you must set the Authorization header yourself in code.
var http = require('http');
var options = {
host: 'www.tempuri.org',
path: '/helloworld/',
headers: {
'Authorization': 'Basic QWxhZGRpbjpPcGVuU2VzYW1l'
}
};
http.request(options, function(response) {
var body = '';
response.on('data',function(c) {
body+= c;
})
response.on('end',function() {
console.log(body)
})
}).end();
The following links discuss basic auth, how the browser encodes the data in the URL, and how you could implement it yourself in Node.js:
Basic access authentication (Wikipedia)
Can you pass user/pass for HTTP Basic Authentication in URL parameters? (serverfault)
Basic HTTP authentication in Node.js using the request module (Hay Kranen)

Authorization Header appended only once in AJAX CORS request

I'm calling my RESTful API from Javascript in a CORS scenario.
I'm using JQuery to send my POST authenticated request.
Here is an example:
function post(settings, addAccessToken) {
settings.type = 'POST';
settings.cache = false;
if (settings.dataType === undefined)
settings.dataType = 'json';
if (addAccessToken) {
settings.xhrFields = { withCredentials: true };
settings.beforeSend = function (request) {
request.setRequestHeader('Authorization', 'Bearer <my access token>');
};
settings.headers = {
'Authorization': 'Bearer <my access token>'
};
}
return $.ajax(settings);
}
On server side, I can see the first call coming with the 'Authorization' Header correctly valued, while all the others don't have such Header.
What am I missing?
Thank you
cghersi
I solved my issue so I want to give the answer to everybody else is in the same situation.
1) The problem was to enable OPTIONS http request from server-side. In fact, there is a first call to the same url but with verb 'OPTIONS' and then a second call to the real url with POST|GET method. If the server doesn't properly answer to the first 'OPTIONS' call, e.g. specifying the correct Allowed Headers etc., the second call doesn't work.
2) The notation
settings.headers = {
'Authorization': 'Bearer <my access token>'
};
is not working. The only way to setup an header is:
settings.beforeSend = function (request) {
request.setRequestHeader('Authorization', 'Bearer <my access token>');
};
Hope this can help other people in the future.
cghersi

Categories