Using fetch to get access token from Django OAuth Toolkit - javascript

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

Related

App Script Shopify GraphQL Request - 400 response

I am trying to call Shopify's GraphQL API via Google App Script.
I have successfully made the call via Postman using the same body and authentication values and that has been working fine.
However, when calling the API via App Script I keep receiving a 400 response code, with the following error message {"errors":{"query":"Required parameter missing or invalid"}}
Here is the code I am using:
function shopifySync() {
var url = "https://store-name.myshopify.com/admin/api/2021-07/graphql.json";
var payloaddata = `query {orders(first: 20) { edges { node { id } } } }`;
var payload = JSON.stringify(payloaddata);
var password = "api_password"; //Private Shopify App
var response = UrlFetchApp.fetch(url, {
'method': "POST",
'muteHttpExceptions': true,
'headers': { "X-Shopify-Access-Token": password , "Content-Type": "application/json"},
'payload': payload
},
);
Logger.log(response.getContentText());
Logger.log(response.getResponseCode());
}
In Shopify's documentation the 400 HTTP response is classified as "Bad Request" and the explanation is:
The request was not understood by the server, generally due to bad syntax or because the Content-Type header was not correctly set to application/json.
This status is also returned when the request provides an invalid code parameter during the OAuth token exchange process.
Source: https://shopify.dev/api/usage/response-codes
How can I resolve this issue and successfully call Shopify's GraphQL API?
I found the solution in the GraphQL documentation of another Saas company.
The issue was how the payload was formatted
How I tried it:
'payload': payload
How it should be:
'payload': JSON.stringify({'query': payloaddata})
Final code that is working for me with the 2021-07 GraphQL API for Shopify:
var url = "https://store-name.myshopify.com/admin/api/2021-07/graphql.json";
var payloaddata = 'query {orders(first: 20) { edges { node { id } } } }';
var password = "api_password";
var response = UrlFetchApp.fetch(url, {
'method': "POST",
'muteHttpExceptions': true,
'headers': { "X-Shopify-Access-Token": password , "Content-Type": "application/json"},
'payload': JSON.stringify({'query': payloaddata})
});

Response 400 for Fetch API call

I am trying to make a call using JavaScript's Fetch API to generate an OAuth Token but I keep receiving a 400 response code and I'm not sure why. I wrote the key and secret to the console to verify their values, and I made the same API call using cURL (with the response I expected). Is there a small issue in my syntax?
fetch('https://api.petfinder.com/v2/oauth2/token', {
method: 'POST',
body: 'grant_type=client_credentials&client_id=' + key + '&client_secret=' + secret
}).then(r => { response = r.json() });
If the request body is a string, the Content-Type header is set to text/plain;charset=UTF-8 by default. Since you're sending urlencoded data, you have to set the Content-Type header to application/x-www-form-urlencoded.
fetch('https://api.petfinder.com/v2/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'grant_type=client_credentials&client_id=' + key + '&client_secret=' + secret
})
As I mentioned in a comment, you shouldn't make the above request from a browser since it exposes the client secret.
Thanks to #Arun's recommendation of adding Content-Type, I am getting the right response now.
Also, for any other JavaScript newbies playing around with the petfinder API, this is the chain that I used to extract the token from the response:
fetch('https://api.petfinder.com/v2/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'grant_type=client_credentials&client_id=' + key + '&client_secret=' + secret
}).then(response => response.json().then(data => ({
data: data,
status: response.status})
).then(function(res) {
console.log(res.status, res.data.access_token);
}));

Authorization Header missing on POST request [duplicate]

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

How to use Stripe's secret key and publishable key?

I would like to connect with the Stripe API using a https call using the https library.
var https = require('https');
I have gotten the secret key and publishable key and put it inside a object:
var stripe = {
secret_key: 'secret_key_given_in_the_dashboard',
publishable_key: 'publishable_key_given_in_the_dashboard'
}
I am now in the part of creating the requestDetail object:
var requestDetails = {
'protocol' : 'https:',
'hostname' : 'api.stripe.com',
'method' : 'POST', //WHICH OF POST GET PUT DELETE SHOULD I USE?
'path' : '???????????????????????',// WHICH ENDPOINT SHOULD I USE?
'auth' : '???????????????????????',// SHOULD I USE THE SECRET AND PUBLISHABLE KEY HERE?
'headers' : {
'Content-Type' : 'application/x-www-form-urlencoded',
'Content-Length' : Buffer.byteLength(stringPayload)
}
};
I plan to make use of the requestDetails object in the call using https:
var req = https.request(requestDetails, function(res){
// Grab the status of the sent request
var status = res.statusCode;
//Callback successfully if the request went through
if(status == 200 || status == 201) {
callback(false);
} else {
callback('Status code returned was ' + status);
}
});
Where and how should I use the secret key and publishable key in order to make a call to the stripe API?
Which endpoint?
Which method (POST, GET, PUT,or DELETE)?
I would like to eventually create a order and pay through the STRIPE api.
But for now just any authenticated call through the stripe api will do as I need a sample format that works....
I'm not too sure where to add the secret key and publishable key....
You should install official stripe package (source: https://github.com/stripe/stripe-node), require the package and authenticate it using your secret key ( example from the github docs):
const stripe = require('stripe')('your_stripe_secret_key');
stripe.customers.create({
email: 'customer#example.com',
})
.then(customer => console.log(customer.id))
.catch(error => console.error(error));
The package is an abstraction to make the API requests for you.
More docs: https://stripe.com/docs/api?lang=node
However, if you want to use the https directly for Stripe API requests, which is not recommended, you can check the docs and examples for using the cURL, since it shows the endpoints for each example.
https://stripe.com/docs/api/authentication?lang=curl
try using fetch, 'Authorization': 'Bearer ' + sk.
My working example of retrieving a customer based on the customer_id:
const url = `https://api.stripe.com/v1/customers/${stripe_customer_id}`;
return await fetch(url, {
method: "get",
headers: {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + sk,
}
})
.then(function(response) {
return response.json();
})
.then(function(response) {
// console.log(response);
return response;
});
};

Fetch, set-cookies and csrf

I m using Isomorphic fetch in my application and I m having some troubles dealing with CSRF.
Actually, I m having a backend that sends me a CSRF-TOKEN in set-cookies property :
I have read somewhere that it's not possible, or it's a bad practice to access this kind of cookies directly inside of my code.
This way, I tried to make something using the credentials property of fetch request :
const headers = new Headers({
'Content-Type': 'x-www-form-urlencoded'
});
return this.fetcher(url, {
method: 'POST',
headers,
credentials: 'include',
body: JSON.stringify({
email: 'mail#mail.fr',
password: 'password'
})
});
This way, I m able to send my CSRF cookie back to my server to serve my need (it's a different one, because it s not the same request) :
My problem
My problem is that my backend needs to receive a x-csrf-token header and so I can't set it to my POST request.
What I need
How can I do to put the value of set-cookies: CSRF-TOKEN into the next request x-csrf-token header ?
It looks like in your scenario you are supposed to read from CSRF-TOKEN cookie. Otherwise it would be marked HttpOnly as JSESSIONID. The later means you cannot access it from the web page but merely send back to server automatically.
In general there is nothing wrong in reading CSRF token from cookies. Please check this good discussion: Why is it common to put CSRF prevention tokens in cookies?
You can read your cookie (not HttpOnly, of cause) using the following code
function getCookie(name) {
if (!document.cookie) {
return null;
}
const xsrfCookies = document.cookie.split(';')
.map(c => c.trim())
.filter(c => c.startsWith(name + '='));
if (xsrfCookies.length === 0) {
return null;
}
return decodeURIComponent(xsrfCookies[0].split('=')[1]);
}
So fetch call could look like
const csrfToken = getCookie('CSRF-TOKEN');
const headers = new Headers({
'Content-Type': 'x-www-form-urlencoded',
'X-CSRF-TOKEN': csrfToken
});
return this.fetcher(url, {
method: 'POST',
headers,
credentials: 'include',
body: JSON.stringify({
email: 'test#example.com',
password: 'password'
})
});
Yes header name depends on your server. For example django usecase to setup CSRF token using fetch is like this:
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=UTF-8',
'X-CSRFToken': get_token
},

Categories

Resources