This question already has an answer here:
Error when accessing API with fetch while setting mode to 'no-cors' [duplicate]
(1 answer)
Closed 2 years ago.
I'm trying to send a POST request to a URL, using fetch().
I'm deliberately setting the Authorization header but when I inspect the response in Firefox Dev Tools it outputs following error "Missing request header 'Authorization' for method parameter of type String".
var target_url = "https://api.sonos.com/login/v3/oauth/access";
var encoded_msg = btoa(client_id + ':' + secret); // base64-encodes client_id and secret using semicolon as delimiter
var params = `grant_type=authorization_code` + `&code=${authCode}` + `&redirect_uri=${redirect_uri}`;
var myHeaders = new Headers({
'Authorization': `Basic ${encoded_msg}`,
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Content-Length': params.length,
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
});
fetch(target_url, {
method: 'POST',
mode: 'no-cors',
credentials: 'include',
redirect: 'follow',
headers: myHeaders,
body: params
})
.then(response => {
console.log("Status: " + response.status);
console.log("StatusText: " + response.statusText);
console.log("Type: " + response.type);
console.log("URL: " + response.url);
});
What removes the Authorization-Header, why and how do I prevent it?
Edit:
For Clarification, I'm using Firebase Cloud Functions to host my webpage from which I send the request to the Sonos Authorization API.
Using Postman, the request goes through and I get the correct response.
The step you're performing to retrieve an access token must be performed in a Cloud function endpoint.
Get an access token
Once you’ve gotten an authorization code, use it to get an access token and refresh token. Use the access token to send API calls to the household through the Sonos cloud. This step uses your client secret, so it should be a server-side request rather than a client-side (browser-based) request.
Reference:
https://developer.sonos.com/build/direct-control/authorize/
Bring in node-fetch as a dependency in your package.json because it's API implementation follows closely with browser fetch.
Add an endpoint as follows:
const fetch = require('node-fetch');
const functions = require('firebase-functions');
const secret = functions.config().sonos.secret;
const client_id = functions.config().sonos.client_id;
const redirect_uri = functions.config().sonos.redirect_uri;
exports.retrieveAccessToken = functions.https.onRequest(async (req, res) => {
const {authCode} = req.query;
const target_url = "https://api.sonos.com/login/v3/oauth/access";
const encoded_msg = btoa(`${client_id}:${secret}`); // base64-encodes client_id and secret using semicolon as delimiter
const body = `grant_type=authorization_code&code=${authCode}&redirect_uri=${redirect_uri}`;
const headers = new Headers({
'Authorization': `Basic ${encoded_msg}`,
'Content-Length': body.length,
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
});
const response = await fetch(target_url, {
method: 'POST',
redirect: 'follow',
headers,
body
});
const token_data = await response.json();
return token_data;
});
Modify code in your webpage to make a request to the cloud function endpoint after user returns from Sonos login service.
const authCode = new URLSearchParams(window.location.search).get('code');
fetch(`https://us-central1-<project-id>.cloudfunctions.net/retrieveAccessToken?authCode=${code}`);
Related
I'm trying to use the fetch API in vanilla JavaScript to generate a token provided by Django OAuth Toolkit. The application I've created in DOT uses the "Resource owner password-based" authorization grant type. My code looks like this (grant_type, username and password are provided through request.formData()):
const data = await request.formData();
const oauth = await fetch(`${API_ROOT}/o/token`, {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Basic ${Buffer.from(CLIENT_ID + ':' + CLIENT_SECRET).toString('base64')}`
},
body: data
});
This request imitates a successful GET request I've created using Insomnia (with Multipart Form data for grant_type, username and password + CLIENT_ID and CLIENT_SECRET as the username and password in Basic Auth). In other words, I don't understand why the JavaScript fetch request does not work even though it is supposed to be identical to the Insomnia request. The JavaScript fetch request returns a 400 error. When I remove the Content-Type header, I get a 500 error. What am I doing wrong?
EDIT: It may be worth noting that I am making this fetch call within a SvelteKit application.
As it turns out, in this particular case I DID need to set Content-Type. I found this answer: Trying to get access token with django-oauth-toolkit using fetch not working while working using jquery
My code works as follows:
const data = await request.formData();
const oauth = await fetch(`${API_ROOT}/oauth/token/`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
Authorization: `Basic ${Buffer.from(CLIENT_ID + ':' + CLIENT_SECRET).toString('base64')}`,
},
body: formDataToUrlEncoded(data)
});
The formDataToUrlEncoded function roughly ressembles the one posted in the above post:
export function formDataToUrlEncoded(formData) {
var url_encoded = '';
for (var pair of formData) {
if (url_encoded != '') {
url_encoded += '&';
}
url_encoded += encodeURIComponent(pair[0]) + '=' + encodeURIComponent(pair[1]);
}
return url_encoded;
}
I've been trying to get all candidates from Catsone into a Google Sheet and although the code is apparently according to their API instruction, I'm getting the above mentioned error and I'm not sure where to look for the issue.
Here's the code I'm running:
const API_KEY = "XXXXXXXXXXXXXXXXXXXXXXXX";
function getallcandidates() {
const url = 'https://api.catsone.com/v3/candidates';
const params = {
'muteHttpExceptions': true,
'method': 'GET',
'headers': {
'Content-Type': 'application/json',
'Authorization': 'Token' + API_KEY
}
};
const response = UrlFetchApp.fetch(url, params);
const data = response.getContentText();
const json = JSON.parse(data);
Logger.log('Data: ' + json)
}
These are their instructions for authentication: https://docs.catsone.com/api/v3/#authentication
This is what successfully I got whe I tried calling it from Postman:
var myHeaders = new Headers();
myHeaders.append("Authorization", "Token XXXXXXXXXXXXXXXXXXXX");
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
fetch("https://api.catsone.com/v3/candidates", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
Appreciate any help.
When I saw your added Javascript and your Google Apps Script, if the value of const API_KEY = "XXXXXXXXXXXXXXXXXXXXXXXX"; has no space at the top character, how about the following modification?
Modified script:
From:
'Authorization': 'Token' + API_KEY
To:
'Authorization': 'Token ' + API_KEY
From your additional Javascript, 'Token' is modified to 'Token '.
Spotify Oauth2 Docs - Client Credential Flow
I asked a similar question, and I was able to get the code running in google apps script (javascript). I am using Client Credential Flow. However, I tried to recreate this code in Nodejs. When running this code, I receive a server error.
error: "server_error"
This is the problem code in Nodejs
const defaultConfig = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
// this encodes the app_id and api_key into a base64 string like the documentation states
'Authorization': `Basic ${btoa(spotify.API_ID + ':' + spotify.API_KEY)}`
},
body: JSON.stringify({
'grant_type': 'client_credentials',
})
};
// this function is wrapped in a list titled, "apiSettings" with other functions. I just included the authorization function here:
const apiSettings = {
getSpotifyAuthorizeToken: async () => {
const endpoint = "https://accounts.spotify.com/api/token";
return await (await fetch(endpoint, defaultConfig)).json();
},
};
I got this similar code to work in google apps script with no problems.
Could the server error be a result of me running Nodejs on a "localhost:xxxx"? The documentation states that, "The Client Credentials flow is used in server-to-server authentication."
if this is not the case, could you provide a working
Spotify API OAUTH2 Server Error Response when requesting token
From your previous question, I would like to propose the following sample scripts.
Sample script 1:
If you want to convert the script of this answer to Node.js and node-fetch, how about the following script?
const spotify = { API_ID: "API_ID", API_KEY: "API_KEY" }; // Please set your client ID and client secret.
const buf = Buffer.from(spotify.API_ID + ":" + spotify.API_KEY);
const para = new URLSearchParams();
para.append("grant_type", "client_credentials");
const defaultConfig = {
method: "POST",
headers: { Authorization: `Basic ${buf.toString("base64")}` },
body: para,
};
const apiSettings = {
getSpotifyAuthorizeToken: async () => {
const endpoint = "https://accounts.spotify.com/api/token";
return await (await fetch(endpoint, defaultConfig)).json();
},
};
Sample script 2:
If you want to convert the script of this answer to Node.js and node-fetch, how about the following script?
const spotify = { API_ID: "API_ID", API_KEY: "API_KEY" }; // Please set your client ID and client secret.
const para = new URLSearchParams();
para.append("grant_type", "client_credentials");
para.append("client_id", spotify.API_ID);
para.append("client_secret", spotify.API_KEY);
const defaultConfig = {
method: "POST",
body: para,
};
const apiSettings = {
getSpotifyAuthorizeToken: async () => {
const endpoint = "https://accounts.spotify.com/api/token";
return await (await fetch(endpoint, defaultConfig)).json();
},
};
Reference:
node-fetch
From a VueJS application I'm attempting to do a simple POST to the Twilio API in order to send an SMS. When the POST is executed I receive the following error:
"Access to XMLHttpRequest at 'https://api.twilio.com/2010-04-01/Accounts/AC42exxxxxxxxxxcfa9c48/SMS/Messages' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field username is not allowed by Access-Control-Allow-Headers in preflight response."
The offending code is the following:
sendTwilio(){
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const sFromNumber = process.env.TWILIO_NUMBER;
const sBaseURL = 'https://api.twilio.com';
const phoneNumber = parsePhoneNumberFromString(this.sms.to_number,'US')
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`,
'username': `${accountSid}`
},
sBodyText='Test'
this.SmsUrl = sBaseURL + '/2010-04-01/Accounts/' + accountSid + '/SMS/Messages';
if (phoneNumber.isValid()){
this.sms.formattedPhone = phoneNumber.number;
this.postData = 'From=' + sFromNumber
+ '+To=' + phoneNumber.number
+ '+Body=' + sBodyText
axios.post(`${this.SmsUrl}`, this.postData, {
headers: headers
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error)
})
}
},
Is the problem with the format used for the username in the header or something with my CORS settings?
My CORS settings are as follows:
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = [
'http://localhost:8000',
'http://localhost:8080',
'http://127.0.0.1:8000'
]
Twilio uses Basic Auth to do authentication, so in your case when doing your POST using axios you need to do:
const headers = {
'Content-Type': 'application/json'
};
const auth = {
username: accountSid,
password: authToken
}
[...]
axios.post(this.SmsUrl, this.postData, {
auth: auth,
headers: headers
})
I'm not sure how you're using this though. Have a look at the comments of the question. You should never expose your Twilio credentials to the client in a browser application.
I am using node.js request module to fetch an id_token from an API. After fetching that id_token I want to send a redirect uri response with a set-cookie header to the redirected url. But I can't quite figure out how to do it.
Here is my code:
app.use("/nodejs-server/retrieveCode", function(req, res) {
var clientID = 'some random string'
var client_Secret = 'another random string'
var code_token = clientID + ":" + client_Secret
var buffer = new Buffer(code_token)
var token = buffer.toString('base64')
var rtoken = "Basic " + token;
var headers = {
'Content-Type': 'application/json',
'Authorization': rtoken
}
var postData = {grant_type: 'authorization_code', code: req.query.code, redirect_uri: 'http://localhost:3000/nodejs-server/retrieveCode'} //Query string data
var options = {
method: 'POST', //Specify the method
body: postData,
url: 'http://localhost:4000/server/token',
json: true,
headers: headers
}
request(options
, function(error, response, body){
if(error) {
console.log(error);
} else {
//send a redirect uri and set-cookie header response
}
});
I tried using
res.redirect(302, "http://localhost:9000");
and it is able to redirect but I am not able to send the cookie with it as well
Any help is appreciated.
After lots of trials and errors and googling I was finally able to achieve my goal. In order to send a cookie with an expiry to the the redirect URL, I just added
const expires = body.exp.toUTCString();
res.cookie('id_token', body.id_token, { expires });
res.redirect(302, 'http://localhost:8080');
in express 4.16.3 , you must set
res.cookie()
before
res.redirect()
and it works for me, without error