I'm trying to make a request to the Amazon Selling Partner API (node.js/Lambda) but I currently always get a 403 error back. I have plugged the same credentials and access token into Postman and the request works fine. I believe that there must be an error when I'm calculating the signature for the request, but I can't see anything wrong.
I'm calculating the signature as follows:
function constructCanonicalRequest(accessToken, dateTime) {
let canonical = [];
canonical.push('GET');
canonical.push('/fba/outbound/2020-07-01/fulfillmentOrders/FBATestOrder-1');
canonical.push('');
canonical.push('host:' + 'sandbox.sellingpartnerapi-na.amazon.com');
canonical.push('x-amz-access-token:' + accessToken);
canonical.push('x-amz-date:' + dateTime);
canonical.push('');
canonical.push('host;x-amz-access-token;x-amz-date');
canonical.push(crypto.SHA256(''));
let canonicalRequest = canonical.join('\n');
let canonicalRequestHash = crypto.SHA256(canonicalRequest);
return canonicalRequestHash
};
function constructStringToSign(dateTime, date, canonicalRequestHash) {
let stringToSign = [];
stringToSign.push('AWS4-HMAC-SHA256')
stringToSign.push(dateTime);
stringToSign.push(date + '/' + 'us-east-1' + '/' + 'execute-api' + '/aws4_request');
stringToSign.push(canonicalRequestHash);
return stringToSign.join('\n');
};
function constructSignature(date, iamSecret, stringToSign) {
let kDate = crypto.HmacSHA256(date, 'AWS4' + iamSecret);
let kRegion = crypto.HmacSHA256('us-east-1', kDate);
let kService = crypto.HmacSHA256('execute-api', kRegion);
let kSigning = crypto.HmacSHA256('aws4_request', kService);
let signature = crypto.HmacSHA256(stringToSign, kSigning).toString(crypto.enc.Hex);
return signature
};
The rest of the function is:
let dateTimeISO = new Date().toISOString();
let dateTime = dateTimeISO.replace(/(\.\d{3})|\W/g,'');
let date = dateTime.split('T')[0];
let canonicalRequestHash = constructCanonicalRequest(accessToken, dateTimeISO);
let stringToSign = constructStringToSign(dateTime, date, canonicalRequestHash);
let signature = constructSignature(date, iamSecret, stringToSign);
let authHeader = 'AWS4-HMAC-SHA256 Credential=' + iamId + '/' + date + '/' + 'us-east-1' + '/execute-api/aws4_request, SignedHeaders=host;x-amz-access-token;x-amz-date, Signature=' + signature
console.log(authHeader);
let amazonUrl = "https://sandbox.sellingpartnerapi-na.amazon.com/fba/outbound/2020-07-01/fulfillmentOrders/FBATestOrder-1";
const amazonResponse = await fetch(amazonUrl, {
method: 'get',
headers: {
'Authorization':authHeader,
'Content-Type':'application/json; charset=utf-8',
'host':'sandbox.sellingpartnerapi-na.amazon.com',
'x-amz-access-token':accessToken,
'user-agent': 'My Selling Tool/2.0 (Language=JS;Platform=Node)',
'x-amz-date':dateTime,
}
});
I have also tried using multiple difference crypto libraries to see if the HMAC creation is the problem, but this hasn't fixed anything.
I have my access working in C#. There is a Java library available on GitHub:
https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/developer-guide/SellingPartnerApiDeveloperGuide.md
I just followed the instructions there, though there is a deficiency in the C# lib about assume role, that is in the java lib.
After seeing through this link, I tried the same in my postman.
var storageAccount = "mystorage";
var accountKey = "<<primaryKey>>";
var date = new Date();
var UTCstring = date.toUTCString();
var data = UTCstring + "\n" + "/mystorage/Health"
var encodedData = unescape(encodeURIComponent(data));
var hash = CryptoJS.HmacSHA256(encodedData, CryptoJS.enc.Base64.parse(accountKey));
var signature = hash.toString(CryptoJS.enc.Base64);
var auth = "SharedKeyLite " + storageAccount + ":" + signature;
postman.setEnvironmentVariable("auth", auth);
postman.setEnvironmentVariable("date", UTCstring);
When I make the request to ATS, to the following url,
I get the auth denied!
Can someone please guide me what's going wrong here?!
I think you need to generate a bearer token and put it to the Authorization of Postman.
If you are using C#, you can use this to get the bearer token:
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
string accesstoken = azureServiceTokenProvider.GetAccessTokenAsync("https://storage.azure.com/").Result;
string bearertoken = "Bearer " + accesstoken;
Then Copy the bearer token:
After that, it should be ok.
Just realized that the url and the data that we are encoding should exactly match the url we are querying...
After changing
var data = UTCstring + "\n" + "/mystorage/Health"
to
var data = UTCstring + "\n" + "/mystorage/Health(PartitionKey='USA',RowKey='WA')"
things started working.
Update :
It just expects the right table query. The following works fine,
var data = UTCstring + "\n" + "/mystorage/Health()"
with all filter expressions in the url being invoked from postman.
I had been following the documentation of Script Based Authentication for Damn Vulnerable Web Application using ZAP. I have navigated to http://localhost/dvwa/login.php through Manual Explore which opens up the DVWA application on my localhost as follows:
and adds the URL to the Default Context.
I've also created the dvwa script with the following configuration:
and modified the dvwa script:
Now when I try Configure Context Authentication, dvwa script does gets loaded but the CSRF field doesn't shows up.
Additionally, POST Data doesn't even shows up but Extra POST Data is shown.
Am I missing something in the steps? Can someone help me out?
The modified script within the documentation of Script Based Authentication section for Damn Vulnerable Web Application using ZAP
seems incomplete.
The complete script is available at Setting up ZAP to Test Damn Vulnerable Web App (DVWA) which is as follows:
function authenticate(helper, paramsValues, credentials) {
var loginUrl = paramsValues.get("Login URL");
var csrfTokenName = paramsValues.get("CSRF Field");
var csrfTokenValue = extractInputFieldValue(getPageContent(helper, loginUrl), csrfTokenName);
var postData = paramsValues.get("POST Data");
postData = postData.replace('{%username%}', encodeURIComponent(credentials.getParam("Username")));
postData = postData.replace('{%password%}', encodeURIComponent(credentials.getParam("Password")));
postData = postData.replace('{%' + csrfTokenName + '%}', encodeURIComponent(csrfTokenValue));
var msg = sendAndReceive(helper, loginUrl, postData);
return msg;
}
function getRequiredParamsNames() {
return [ "Login URL", "CSRF Field", "POST Data" ];
}
function getOptionalParamsNames() {
return [];
}
function getCredentialsParamsNames() {
return [ "Username", "Password" ];
}
function getPageContent(helper, url) {
var msg = sendAndReceive(helper, url);
return msg.getResponseBody().toString();
}
function sendAndReceive(helper, url, postData) {
var msg = helper.prepareMessage();
var method = "GET";
if (postData) {
method = "POST";
msg.setRequestBody(postData);
}
var requestUri = new org.apache.commons.httpclient.URI(url, true);
var requestHeader = new org.parosproxy.paros.network.HttpRequestHeader(method, requestUri, "HTTP/1.0");
msg.setRequestHeader(requestHeader);
helper.sendAndReceive(msg);
return msg;
}
function extractInputFieldValue(page, fieldName) {
// Rhino:
var src = new net.htmlparser.jericho.Source(page);
// Nashorn:
// var Source = Java.type("net.htmlparser.jericho.Source");
// var src = new Source(page);
var it = src.getAllElements('input').iterator();
while (it.hasNext()) {
var element = it.next();
if (element.getAttributeValue('name') == fieldName) {
return element.getAttributeValue('value');
}
}
return '';
}
Using this script, CSRF Field and POST Data field shows up just perfect.
Problem
We are seeing this error returned from the DocumentDB REST API whenever we request a list or query, but not when we fetch objects by name/id:
The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used.
Background
We have been successfully using the node.js sdk with DocumentDB for over a year now, but as we want to migrate our back-end restful API code from a node.js App Service to Azure Functions we are seeing 10-30 second lag times come into play as the DocumentDB sdk loads slowly when the Function hasn't been called in a while. We know that the Function instance is hot, and this isn't a cold instance issue based on previous communication with the Azure Functions team.
To work around this we want to test the DocumentDB REST API which requires zero external libraries to run in a node.js Function and should execute as quickly as possible.
Code
This is the test harness running in local node.js. We'll move this to an Azure Function once it's working.
var express = require('express');
var router = express.Router();
var crypto = require("crypto");
var request = require('request');
router.get('/', function (req, res, next) {
var key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
var uri = "https://xxxxxx.documents.azure.com";
var verb = 'GET';
var type = 'dbs';
var link = 'dbs';
var url = `${uri}/${link}`;
var headers = getDefaultRequestHeaders();
// var body = `{"query":"SELECT * FROM c", "parameters": []}`;
var body = '';
headers['content-length'] = body.length;
headers['authorization'] = getAuthorizationTokenUsingMasterKey(verb, type, link, headers['x-ms-date'], key);
request[verb.toLowerCase()]({"url": url, "headers": headers, "body": body}, function (error, response, body) {
// console.log(`error is ${error}`);
// console.log(`response is ${JSON.stringify(response, null, 2)}`);
console.log(`body is ${body}`);
res.status(response.statusCode).json(body);
});
});
function getDefaultRequestHeaders(isQuery, date) {
var headers = {
"content-type": "application/json",
"x-ms-date": new Date().toUTCString(),
"x-ms-version": "2017-02-22",
"accept": "application/json",
"cache-control": "no-cache",
"user-agent": "xxxxxx/1.0"
};
if(isQuery) {
headers["x-ms-documentdb-isquery"] = true;
headers["content-type"] = "application/query+json";
}
if(date) {
headers["x-ms-date"] = date;
}
return headers;
}
function getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceLink, date, masterKey) {
var key = new Buffer(masterKey, "base64");
var text = (verb || "").toLowerCase() + "\n" +
(resourceType || "").toLowerCase() + "\n" +
(resourceLink || "") + "\n" +
date.toLowerCase() + "\n" +
"" + "\n";
var body = new Buffer(text, "utf8");
var signature = crypto.createHmac("sha256", key).update(body).digest("base64");
var MasterToken = "master";
var TokenVersion = "1.0";
return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
}
module.exports = router;
We are using the getAuthorizationTokenFromMasterKey function verbatim from the Access control in the DocumentDB API page.
The key, app name, and user-agent have been replaced with x's for privacy/security.
Test Results
List Databases
When I try the most basic call to list dbs the server returns the token error:
var verb = 'GET';
var type = 'dbs';
var link = 'dbs';
Response:
"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ndbs\n\nsat, 12 aug 2017 12:28:41 gmt\n\n'\r\nActivityId: acbf19d9-6485-45c5-9c30-6aa21f14d5b3\"}"
Get Database
However, when I perform the get database request it works fine:
var verb = 'GET';
var type = 'dbs';
var link = 'dbs/00001';
Response:
"{\"id\":\"00001\",\"_rid\":\"0eUiAA==\",\"_ts\":1441256154,\"_self\":\"dbs\/0eUiAA==\/\",\"_etag\":\"\\"00007d4a-0000-0000-0000-55e7d2da0000\\"\",\"_colls\":\"colls\/\",\"_users\":\"users\/\"}"
List Collections
Similarly, requesting the list of collections from this database returns a token error:
var verb = 'GET';
var type = 'colls';
var link = 'dbs/00001/colls';
Respose:
"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ncolls\ndbs/00001\nsat, 12 aug 2017 12:32:19 gmt\n\n'\r\nActivityId: 8a9d4ff8-24ef-4fd2-b400-f9f8aa743572\"}"
Get Collection
But when I call get collection I get a valid response:
var verb = 'GET';
var type = 'colls';
var link = 'dbs/00001/colls/00001';
Response:
"{\"id\":\"00001\",\"indexingPolicy\":{\"indexingMode\":\"consistent\",\"automatic\":true,\"includedPaths\":[{\"path\":\"\/*\",\"indexes\":[{\"kind\":\"Range\",\"dataType\":\"Number\",\"precision\":-1},{\"kind\":\"Range\",\"dataType\":\"String\",\"precision\":-1},{\"kind\":\"Spatial\",\"dataType\":\"Point\"}]}],\"excludedPaths\":[]},\"_rid\":\"0eUiAJMAdQA=\",\"_ts\":1454200014,\"_self\":\"dbs\/0eUiAA==\/colls\/0eUiAJMAdQA=\/\",\"_etag\":\"\\"00000100-0000-0000-0000-56ad54ce0000\\"\",\"_docs\":\"docs\/\",\"_sprocs\":\"sprocs\/\",\"_triggers\":\"triggers\/\",\"_udfs\":\"udfs\/\",\"_conflicts\":\"conflicts\/\"}"
List Documents
Requesting list documents on that collection gives me this error:
var verb = 'GET';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs';
Response:
"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ndocs\ndbs/00001/colls/00001\nsat, 12 aug 2017 12:34:48 gmt\n\n'\r\nActivityId: 57097e95-c41b-4770-b91a-370418ef2cce\"}"
Get Document
Not surprisingly, fetching a single document works fine:
var verb = 'GET';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs/e7fe638d-2152-2097-f9c6-9801d7cf5cdd';
Response:
"{\"name\":\"test rest api\",\"id\":\"e7fe638d-2152-2097-f9c6-9801d7cf5cdd\",\"_rid\":\"0eUiAJMAdQCbHgAAAAAAAA==\",\"_self\":\"dbs\/0eUiAA==\/colls\/0eUiAJMAdQA=\/docs\/0eUiAJMAdQCbHgAAAAAAAA==\/\",\"_etag\":\"\\"0d00d1ee-0000-0000-0000-598ef7d40000\\"\",\"_attachments\":\"attachments\/\",\"_ts\":1502541779}"
Query Documents
Finally, sending a query also results in a token error:
var verb = 'POST';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs';
var body = `{"query":"SELECT * FROM c", "parameters": []}`;
Response:
"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'post\ndocs\ndbs/00001/colls/00001\nsat, 12 aug 2017 12:35:42 gmt\n\n'\r\nActivityId: b8b95f8c-1339-423e-b0e7-0d15d3056180\"}"
I believe the documentation is incorrect. Where they say resourceLink, they should actually say resource id. If you look at the Node SDK code, this is how they are calculating the authorization header (notice the use of resourceId):
getAuthorizationTokenUsingMasterKey: function (verb, resourceId, resourceType, headers, masterKey) {
var key = new Buffer(masterKey, "base64");
var text = (verb || "").toLowerCase() + "\n" +
(resourceType || "").toLowerCase() + "\n" +
(resourceId || "") + "\n" +
(headers["x-ms-date"] || "").toLowerCase() + "\n" +
(headers["date"] || "").toLowerCase() + "\n";
var body = new Buffer(text, "utf8");
var signature = crypto.createHmac("sha256", key).update(body).digest("base64");
var MasterToken = "master";
var TokenVersion = "1.0";
return "type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature;
},
So if you want to list the databases, because there is no resource id you will need to use an empty string for your link variable. Similarly, if you want to list collections in the database, the link should actually be the id of the database (e.g. dbs/00001 and not dbs/00001/colls).
I was getting the same issue. For querying documents I was getting authorization token error. It was due to wrong ResourceId/ResourceLink
var verb = 'POST';
var type = 'docs';
var link = 'dbs/{db-id}/colls/{coll-id}/docs';
var url = `${uri}/${link}`;
var resourceLink = "dbs/{db-id}/colls/{coll-id}"
getAuthorizationTokenUsingMasterKey(verb, type, resourceLink, headers['x-ms-date'], key)
the only correction is required from the given question data is to change the appropriate resourceLink while generating AuthorizationToken. For querying Documents the resourceLink is <dbs/{db-id}/colls/{coll-id}> instead of <dbs/{db-id}/colls/{coll-id}/docs>
I want to provide another thing to consider in addressing this issue. In my case I had to add this header: x-ms-documentdb-query-enablecrosspartition: true, because I created my container with a partitionKey.
I also want to confirm how my main parameters were setup to calculate the authorization header for querying over Documents:
resourceType: docs
resourceLink: dbs/<databaseId>/colls/<containerId>
I had thought, up to this point, that the resourceLInk had to match with the request URL but this is showing me I was wrong. Similarly notice that the resourceType is not present in the resourceLink.
POST /dbs/<databaseId>/colls/<containerId>/docs HTTP/1.1
accept: application/json
x-ms-documentdb-isquery: true
x-ms-version: 2018-12-31
authorization: type%3Dmaster%26ver%3D1.0%26sig%***********************
x-ms-date: Sat, 03 Apr 2021 22:34:24 GMT
x-ms-documentdb-query-enablecrosspartition: true
x-correlation-id: be1b1fe1-94cc-11eb-a0a4-38f9d3924940
Host: <host>.documents.azure.com
User-Agent: AHC/1.0
Connection: keep-alive
Content-Type: application/query+json
Content-Length: 72
{
"query": "SELECT * FROM <containerId>",
"parameters": [
]
}
I got the same error while making an update to document DB but in my case I realized I was using the Read-Only keys. After changing connection string to use it to Read-Write Keys, I was able to update the records.
I am new user on stackoverflow as well as in office 365 development using node.js.
I am successfully getting User(my own office 365 account) mails,calendar events using this tutorial (https://dev.outlook.com/RestGettingStarted/Tutorial/node)
but when i am trying to Create an Event in my calender it gives me below error
"{"error":{"code":"ErrorAccessDenied","message":"Access is denied. Check credentials and try again."}}"
Please provide me suggestions on the same.
Below is the code for creating event which i copied from [https://msdn.microsoft.com/office/office365/APi/calendar-rest-operations#CreateEvents] here
function createEvent(response, request) {
var cookieName = 'node-tutorial-token';
var cookie = request.headers.cookie;
// if (cookie && cookie.indexOf(cookieName) !== -1) {
console.log("Cookie: ", cookie);
// Found our token, extract it from the cookie value
var start = cookie.indexOf(cookieName) + cookieName.length + 1;
var end = cookie.indexOf(';', start);
end = end === -1 ? cookie.length : end;
var token = cookie.substring(start, end);
console.log("Token found in cookie: " + token);
var event = new outlook.Microsoft.OutlookServices.Event();
event.subject = 'Your Subject';
event.start = new Date("October 30, 2014 11:13:00").toISOString();
event.end = new Date("October 30, 2014 12:13:00").toISOString();
// Body
event.body = new outlook.Microsoft.OutlookServices.ItemBody();
event.body.content = 'Body Content';
event.body.contentType = outlook.Microsoft.OutlookServices.BodyType.Text;
// Location
event.location = new outlook.Microsoft.OutlookServices.Location();
event.location.displayName = 'Location';
// Attendee
var attendee1 = new outlook.Microsoft.OutlookServices.Attendee();
var emailAddress1 = new outlook.Microsoft.OutlookServices.EmailAddress();
emailAddress1.name = "abc";
emailAddress1.address = "abc#abcdt.onmicrosoft.com";
attendee1.emailAddress = emailAddress1;
event.attendees.push(attendee1);
var outlookClient = new outlook.Microsoft.OutlookServices.Client('https://outlook.office365.com/api/v1.0',
authHelper.getAccessTokenFn(token));
outlookClient.me.calendar.events.addEvent(event)
.then(function (response) {
console.log(response._Id);
}, function (error) {
console.log(error);
});
}
Make sure your app has requested for calendar.readwrite permission and you need this to create new events. In the example you followed, your app registered for only Calendar.Read permissions (see below).
You should instead go to https://dev.outlook.com/AppRegistration to register an app with Calendar.ReadWrite permission which is required to create new events.