Spotify Authorization from Node Server not working with curl - node.js

I have a node server which gets an access token from Spotify's Web API. The response looks like this:
Response:
{
"statusCode": 200,
"body": "{\"type\":\"success\",\"done\":{\"json\":{\"access_token\":\"BQDqtYhVpafUIMYtZbwmy6iJcC_wvzR9Xrw6bRDFfpL3zZYfkCp2-KZaQVS-ZoElMF1czAl_B1vEaDrtPBOElSV3D5k\",\"token_type\":\"Bearer\",\"expires_in\":3600,\"scope\":\"user-top-read\"}}}",
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token"
}
}
When I try to use the access_token on Spotify's online API tool, I get an error for incomplete JSON. I think this is because the token I generate is only 91 characters long while the code they generate is 171 characters long. Why is my auth code so short?
I want an access token so I can use this react module for accessing my top tracks.
Here is my code for getting the access token:
let getAccessToken = (queryStringParameters) => {
let url = 'https://accounts.spotify.com/api/token';
let encoded = (new Buffer(client_id + ':' + client_secret).toString('base64'));
console.log("encoded = " + encoded);
let params = {
grant_type: 'authorization_code',
username: username,
password: password,
scope: scope
};
const formParams = Object.keys(params).map((key) => {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
return fetch(url, {
method: 'POST',
headers: {
"Authorization": 'Basic ' + encoded,
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formParams
})
.then((response) => {
console.log(util.inspect(response, { showHidden: true, depth: null }));
return response.json();
})
.catch((error) => {
done({
error: error
});
});
};

According to the Authorisation Guide the authorization_code grant type only takes the code and redirect URI values. Not sure why the username and password ones you provide are even accepted, it is possible the token returned is a client credentials one as those tend to be shorter, that only access non-user related endpoints like loading Artist data, but the parameters you're providing appear to be undocumented at least in that guide.

Related

401 failed request when trying to exchange authorization code for tokens w/ google

I'm struggling with this specific step in the Google OAuth process: "Exchange authorization code for tokens" (As called in Google Developers' "OAuth 2.0 Playground", Step 2).
function getGoogleAuthURL() {
const rootUrl = "https://accounts.google.com/o/oauth2/v2/auth";
const options = {
redirect_uri: `http://${HOST}:${PORT}/auth/google`,
client_id: `${googleConfig.clientId}`,
access_type: "offline",
response_type: "code",
prompt: "consent",
scope: defaultScope.join(" "), // A list of scopes declared in the file scope
};
return `${rootUrl}?${querystring.stringify(options)}`;
}
app.get("/auth/google/url", (req, res) => {
res.redirect(getGoogleAuthURL());
});
After redircting the user to the consent prompt, the code above redirect the user to: http://${HOST}:${PORT}/auth/google, giving the 'Authorization code' necessary to get the refresh and access tokens.
app.get(`/auth/google`, async (req, res) => {
const code = req.query.code
const tokens = await getTokens({code})
});
My problem is coming from the getToken() function that is used to POST and return the tokens.
function getTokens({code}) {
const url = 'https://accounts.google.com/o/oauth2/token';
const values = {
code,
client_id: googleConfig.clientId,
client_secret: googleConfig.clientSecret,
redirect_uri: googleConfig.redirect,
grant_type: 'authorization_code',
};
console.log(`${url}${querystring.stringify(values)}`)
return axios
.post(url, querystring.stringify(values), {
headers: {
'Content-Type': 'application/x-www-form-url-encoded',
},
})
.then((res) => res.data)
.catch((error) => {
throw new Error(error.message);
});
}
I get Error: Request failed with status code 401 from the .catch((error) ...
The console.log(${url}${querystring.stringify(values)}) gives the full built link, which is:
https://accounts.google.com/o/oauth2/tokencode=X&client_id=X.apps.googleusercontent.com&client_secret=X&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fdashboard%3B&grant_type=authorization_code
What I troubleshooted:
I've compared many times my 'request' and 'url' with the ones on Google Developers' "OAuth 2.0 Playground", and the syntax doesn't seem to be the problem with the fields, though I have doubt about the punctuation between /token and code=... that I changed to /token and ?code=..., but still resulting in a failed request (401).
I've also been using two different ${url}:
https://oauth2.googleapis.com/token
https://accounts.google.com/o/oauth2/v2/auth
But I can tell which one I should be using.
The second param of axios post is the body, not query params.
You can set params in the third argument.
axios.post(url, {}, {
headers: { 'Content-Type': 'application/x-www-form-url-encoded' },
params: {
tokencode: values.code,
// etc
}
}
You are also missing key for code in your values object.
You can also just append the querystring in the first argument:
axios.post(url + querystring.stringify(values),{}, {
headers: {
'Content-Type': 'application/x-www-form-url-encoded'
},
})

node-fetch send post request with body as x-www-form-urlencoded

i want to send a post request using node-fetch with a body payload encoded in the x-www-form. I tried this code but unfortunately it doesnt work:
paypalSignIn = function(){
var username = process.env.PAYPALID;
var password = process.env.PAYPALSECRET;
var authContent = 'Basic '+base64.encode(username + ":" + password);
fetch('https://api.sandbox.paypal.com/v1/oauth2/token', { method: 'POST',
headers: {
'Accept': 'application/json',
'Accept-Language' :"en_US",
'Authorization': authContent,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'grant_type=client_credentials' })
.then(res => res.json()) // expecting a json response
.then(json => console.log(json));
}
I'm not sure if this way is possible but i need to use this standard for die paypal api.
I'm getting statu code 400 with error
grant_type is null
Thx
I don't know if this is the only error, but at the very least you need a space between the word Basic and the encoded username/password.
Next time you ask a question, also post what your script returned. I'm guessing it was a 401 error in this case.
I used the PayPal sandbox today, here is how I managed to get my access token and a successful response (and also answering the OP's question about sending application/x-www-form-urlencoded POST requests with data) =>
I did it with node-fetch but the plain fetch API should work the same.
import fetch from "node-fetch";
export interface PayPalBusinessAccessTokenResponseInterface {
access_token: string;
}
export interface PayPalClientInterface {
getBusinessAccessToken: (
clientId: string,
clientSecret: string
) => Promise<PayPalBusinessAccessTokenResponseInterface>
}
const paypalClient: PayPalClientInterface = {
async getBusinessAccessToken(
clientId: string,
clientSecret: string
): Promise<PayPalBusinessAccessTokenResponseInterface> {
const params = new URLSearchParams();
params.append("grant_type", "client_credentials");
const paypalAPICall = await fetch(
"https://api-m.sandbox.paypal.com/v1/oauth2/token",
{
method: "POST",
body: params,
headers: {
"Authorization": `Basic ${Buffer.from(clientId + ":" + clientSecret).toString('base64')}`
}
}
);
const paypalAPIRes = await paypalAPICall.json();
return paypalAPIRes;
}
};
export default paypalClient;

Power BI API returning 403 forbidden when fetching dashboard in My workspace

I would try the Power BI API.
So I start by getting Embed Token ( I'm using application owns data scenario ).
1.access Token
var options = {
'method': 'POST',
'url': `https://login.microsoftonline.com/${process.env.TENANT_ID}/oauth2/token`,
'headers': {
'Content-Type': 'multipart/form-data'
},
formData: {
'grant_type': process.env.GRANT_TYPE,
'client_id': process.env.CLIENT_ID,
'client_secret': process.env.CLIENT_SECRET,
'resource': process.env.RESSOURCE,
'Scope': process.env.SCOPE
}
};
// get Access token from AAD to retrieve Embed Token in PBI API
let response;
try {
response = await new Promise((resolve, reject) => {
request(options, (error, response, data) => {
if (error) reject(error)
else resolve(data)
})
})
}
catch (error) {
context.error(error)
}
2.Fetch embed Token (Docs)
var data = '{accessLevel:"View"}';
var config = {
method: 'post',
url: `https://api.powerbi.com/v1.0/myorg/groups/${process.env.GROUP_ID}/dashboards/${process.env.DASHBOARD_ID}/GenerateToken`,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${JSON.parse(response).access_token}`
},
data: data
};
const embedtoken = await axios(config)
context.res = {
// status: 200, /* Defaults to 200 */
body: embedtoken.data
};
3. I have delegated permissions on azure
I'm getting an embed token.
4. fetch dashboard infos
I'm using postman to fetch dashboard infos from the same group id and dashboard id that I mentioned in the api to get embed token
( I add that token in authorization section )
The problem is that I'm getting 403 Forbidden error.
PS: In this post some limitations of service principal method are mentioned . is it the source of my problem ? Am I obliged to use master user method ?
You have a misunderstanding of the usage of Embed Token. It cannot be used to call https://api.powerbi.com/v1.0/myorg/groups/{group id}/dashboards/{dashboard id}/ directly. AAD token is needed here.
To use an Embed Token, you should call the Embed URL with it.
The format of Embed URL is like:
https://app.powerbi.com/reportEmbed?reportId=f6bfd646-b718-44dc-a378-b73e6b528204&groupId=be8908da-da25-452e-b220-163f52476cdd&config=eyJjbHVzdGVyVXJsIjoiaHR0cHM6Ly9XQUJJLVVTLU5PUlRILUNFTlRSQUwtcmVkaXJlY3QuYW5hbHlzaXMud2luZG93cy5uZXQiLCJlbWJlZEZlYXR1cmVzIjp7Im1vZGVybkVtYmVkIjp0cnVlfX0%3d
An .net example:
// You need to provide the workspaceId where the dashboard resides.
ODataResponseListReport reports = await client.Reports.GetReportsInGroupAsync(workspaceId);
// Get the first report in the group.
Report report = reports.Value.FirstOrDefault();
// Generate Embed Token.
var generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
EmbedToken tokenResponse = client.Reports.GenerateTokenInGroup(workspaceId, report.Id, generateTokenRequestParameters);
// Generate Embed Configuration.
var embedConfig = new EmbedConfig()
{
EmbedToken = tokenResponse,
EmbedUrl = report.EmbedUrl,
Id = report.Id
};
Then you can call the EmbedUrl with EmbedToken.
Reference here.

Axios post fails with 403 CSRF token validation failed but works fine in Postman

I have tried everything and can't get Axios to work with SAP Odata Post services. The problem is CSRF token validation failing but its working fine in Postman.
My request looks like this:
const postNewTasks = async (body, headers) => (await axios.get(getHeadersandCFRSURL, {
headers: { 'authorization': auth, 'x-csrf-token': 'fetch' },
withCredentials: true
}).then((response) => {
axios({
method: 'POST',
url: postBatchOperationsURL,
headers: {
"Authorization": auth,
"Content-Type": "multipart/mixed; boundary=batch_1",
"X-CSRF-Token": response.headers["x-csrf-token"], // set CSRF Token for post or update
},
withCredentials: true,
body: body
}).then(function (response) {
console.log(response)
return response
}).catch(function (err) {
console.log(err)
return err
})
})
)
Anybody has idea why the CSRF token validation fails with this axios request?
I had this issue recently and a solution that worked for me was to add a Cookie header with the cookies from the initial response set-cookie headers.
Postman does this automatically, but axios doesn't it would seem. My code from that part after "x-csrf-token":"fetch":
var xcsrftoken = response.headers["x-csrf-token"];
var cookies = '"';
for (var i = 0; i < response.headers["set-cookie"].length; i++) {
cookies += response.headers["set-cookie"][i] + ";";
}
cookies += '"';
axiosClient.defaults.headers.common[this.xcsrftokenName] = xcsrftoken;
axiosClient.defaults.headers.common["Cookie"] = cookies;
axiosClient is the object made from axios.create. I've set those headers as default so that I don't need to include them later in the requests. There were multiple set-cookie headers as well and it was necessary to combine them into one.

DBS API Server Error 500 on Sandbox calls

I was just experimenting with the DBS API Sandbox here (https://www.dbs.com/developers/). We have to use OAuth2 to get the access token before passing it in the header to authenticate API calls. When I tested everything out on their test client UI I got the results back from the API call.
However, when I coded my simple node app, I could successfully perform OAuth2 and get an access token, but when passed in together with the clientId, got a server error 500 from the API.
For example, the GET call to get a bank account details:
const options = {
method: 'GET',
uri: dbsHost + rootPath + '/accounts/22909292550739201999095',
headers: {
'Content-Type': 'application/json',
'accessToken': accessToken,
'clientId': clientId
}
}
request(options, (error, response, body) => {
// Server error 500
console.log(response)
})
These were the exact instructions as those given on the test client UI. On closer inspection I realised the access token I always got back had 488 characters, but that of the test client UI only had 475. Any reason for this difference? How do I know OAuth2 was performed correctly and I'm getting the right access token?
Here's how I did OAuth2:
On the frontend:
let authorizationUri = dbsHost + rootPath + '/oauth/authorize'
authorizationUri += '?client_id=' + clientId
authorizationUri += '&redirect_uri=' + redirectUri
authorizationUri += '&scope=Read'
authorizationUri += '&response_type=code'
authorizationUri += '&state=0399'
windows.location.assign(authorizationUri)
And at the redirect_uri endpoint once I get back the authorization code:
app.get('/authCallback', (req, res) => {
let authCode = req.query.code
// Encode client_id:client_secret in base-64 to pass in as Auth Basic Header
let b64 = new Buffer(clientId + ':' + clientSecret).toString('base64')
const options = {
method: 'POST',
url: dbsHost + rootPath + '/oauth/tokens',
headers: {
'Authorization': 'Basic ' + b64
},
form: {
'code': authCode,
'redirect_uri': redirectUri,
'grant_type': 'token',
'state': '0399'
}
}
request(options, (error, response, body) => {
if (error) {
console.error('Access Token Error', error.message)
return res.status(500).json('Authentication failed');
}
let json = JSON.parse(body)
let accessToken = json['access_token']
// Make a call with this access token
})
})

Resources