Unauthorised (401) error while implementing the HMAC authentication in NodeJs - javascript

I'm trying to implement an API with HAMC authentication in Nodejs. But always I am receiving an Unauthorised (401) error.
I have followed the below instruction for API implementation:
Authentication
Users will authenticate with the web service using HMAC authentication. The following steps are necessary to authenticate with the web service:
Set the Date header on the HTTP Request to the current UTC date/time expressed in RFC 1123 format (example: Mon, 12 Jan 2015 20:50:07 GMT)
Create a “representation” of the request by concatenating the following values into a single string (in this order, with no separators):
2.1 The request method (GET or POST)
2.2 The full request URI (all lowercase) including any query string parameters
2.2.1. Example: https://estimationapi.zirmed.com/1.0/estimate/1234
2.3 The value of the Date header in “yyyy-MM-ddTHH:mm:ss” format (UTC).
2.4 The CustID.
2.5 The Base64 encoded string of the content body (your estimate in the case of a POST).
Compute an HMAC signature by hashing the UTF-8 encoded “representation” with your UTF-8 encoded private WebCallEncryptionKey using HMACSHA256 as the hashing algorithm.
Convert the resultant hash to a Base64 string. This will be the HMAC signature.
Set the Authorization header on the request by concatenating the CustID and HMAC signature separated by a colon. Specify HMACas the authorization scheme.
5.1 Example of an HMAC Authorization header: HMAC 605:pLKb9P8w5e83BMdUH4+m/EeY7O3PsbV5A89KF7IYjnM=
Below is the code I'm using...
const crypto = require("crypto");
const moment = require('moment');
const http = require('http');
let _apiKey = "API_KEY"; // API KEY/HMAC KEY
let _estimateBaseUrl = "http://estimationapi.zirmed.com/1.0";
let _estimateTransaction = "/estimate";
let CustomerID = 'CustomerID';
let BaseAddress = new URL(_estimateBaseUrl);
const utcDate = new Date().toUTCString();
let requestDt = utcDate
let RequestContent = "DATA IN JSON"
getEstimate = async (req, res) => {
let signature = await createRequestAuth(_estimateTransaction, "POST", requestDt, CustomerID, RequestContent);
let response = await getEstimateID(signature);
res.send(response);
};
createRequestAuth = async (uriPath, method, requestDate, custId, body = "") => {
let fullUri = new URL(BaseAddress + uriPath);
let authHashString = await CreateAuthHashString(method, fullUri.toString().toLowerCase(), requestDate, custId, body);
let signature = crypto.createHmac('sha256', _apiKey).update(authHashString).digest("base64");
console.log("signature is === : ", signature)
return signature;
}
getEstimateID = (signature) => {
const data = new TextEncoder().encode( JSON.stringify(RequestContent) );
const options = {
hostname: 'estimationapi.zirmed.com',
port: '',
path: '/1.0/estimate',
method: 'POST',
Headers: {
Authorization: `HMAC ${CustomerID}:${signature}`,
Date: requestDt,
"Content-Type": `application/json`,
'Content-Length': data.length
}
}
console.log("options ------- ", options);
const req = http.request(options, res => {
console.log(`statusCode is: ${res.statusCode}`)
console.log(`res is: ${res}`)
res.on('data', d => { process.stdout.write(d) })
})
req.on('error', error => { console.error('error', error) })
req.write(data)
req.end()
}
CreateAuthHashString = (method, fullUri, requestDate, custId, body) => {
let uri = fullUri.toLowerCase();
let httpMethod = method;
let content = Buffer.from(utf8.encode(body)).toString('base64');
let authHashString = "".concat("", httpMethod,
uri,
moment(requestDate).format('yyyy-MM-DDTHH:mm:ss'),
custId,
content
);
console.log('authHashString: ', authHashString);
return authHashString;
}
module.exports = {
getEstimate
}
Not sure if I have done some silly mistake here but please let me know if anyone is aware of this issue.

2.3 The value of the Date header in “yyyy-MM-ddTHH:mm:ss” format (UTC)
When you use moment(requestDate).format('yyyy-MM-DDTHH:mm:ss'), the formatted value is in local timezone. To format it in UTC, you need to do it as moment(requestDate).utc().format('yyyy-MM-DDTHH:mm:ss').

Related

GitHub API fails with JavaScript FETCH

GitHub seems to have made updates since end of 2021.
https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/
I have followed numerous resources where the below code increases the amount of requests one can do per hour. Now the below request does not work. Instead the documentation says to use CURL, so for instance the below works in a terminal:
curl -u client_id:secret_key https://api.github.com/users/<username>
I want to do this in JavaScript, I am playing around with a GitHub user finder app in JavaScript. Can someone please show me how I can get this to actually work. The code I am using is below.
TL:DR: I can access the github API using the code below and receive a JSON object to display, but it's limited to 60 requests per hour. GitHub documentation says that since end of 2021 query parameters are not allowed anymore so I'm lost now. How can I do this in JavaScript now?
const client_id = "df2429c311a306c35233";
const client_secret = "5c23233326680aa21629451a6401d36ec";
const fetchUsers = async (user) => {
const api_call = await fetch(`https://api.github.com/users/${user}?client_id=df5429c311a306c356f4&
client_secret=${client_secret}`);
const data = await api_call.json();
return {data};
};
EDIT/UPDATE:
const inputValue = document.querySelector("#search");
const searchButton = document.querySelector(".searchButton");
const nameContainer = document.querySelector(".main__profile-name");
const unContainer = document.querySelector(".main__profile-username");
const reposContainer = document.querySelector(".main__profile-repos");
const urlContainer = document.querySelector(".main__profile-url");
const client_id = "<user_id>";
const client_secret = "<client_secret>";
const headers = {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
}
const fetchUsers = async (user) => {
const api_call = await fetch(`https://api.github.com/users/${user}`, {
method: 'GET',
headers: headers
});
const data = await api_call.json();
return {data};
};
const showData = () => {
fetchUsers(inputValue.value).then((res) => {
console.log(res);
nameContainer.innerHTML = `Name: <span class="main__profile-value">${res.data.name}</span>`
unContainer.innerHTML = `Username: <span class="main__profile-value">${res.data.login}</span>`
reposContainer.innerHTML = `Repos: <span class="main__profile-value">${res.data.public_repos}</span>`
urlContainer.innerHTML = `Url: <span class="main__profile-value">${res.data.url}</span>`
})
};
searchButton.addEventListener("click", () => {
showData();
})
Those behave as username and password of the basic authentication type. Hence your Api request should have the following header.
const headers = {
'Authorization': 'Basic ' + btoa(CLIENT_ID + ':' + CLIENT_SECRET)
}
Please note that btoa function is being used because browsers don't have a native support of Buffer. If btoa throws error then try with window.btoa and use it like
const response = await fetch(url, {method:'GET',
headers: headers,
})

Interacting with Oauth1 API in cloudflare worker (Flickr)

I'm needing to interact with the Flickr api from a cloudflare worker, but it's turning out to be exceedingly tricky.
My initial idea was to reach for the oauth1.0a library, but unfortunately it requires being passed a synchronous signature function. This is an issue because I need to use WebCrypto on the worker and it only exposes an asynchronous API.
Are there any other libraries I can use? I've currently spent hours trying to manually craft the request but keep getting errors saying the signature is bad.
This is my current attempt using someone's fork of oauth1.0a that adds support for an async signature function. This currently results in an "invalid_signature" response:
import OAuth from 'oauth-1.0a';
const CALLBACK_URL = "https://localhost:3000/oauth/callback";
const encoder = new TextEncoder();
async function signData(baseString: string, keyString: string) {
return await crypto.subtle.importKey(
'raw',
encoder.encode(keyString),
{ name: 'HMAC', hash: 'SHA-1' },
false,
['sign']
).then(key => {
return crypto.subtle.sign(
"HMAC",
key,
encoder.encode(baseString)
);
}).then(signature => {
let b = new Uint8Array(signature);
// base64 digest
return btoa(String.fromCharCode(...b));
});
}
export async function getRequestToken(consumerKey: string, consumerSecret: string) {
const url = "https://www.flickr.com/services/oauth/request_token";
const token = {
key: consumerKey,
secret: consumerSecret
}
const oauth = new OAuth({
consumer: token,
signature_method: 'HMAC-SHA1',
// #ts-ignore
hash_function: signData,
});
const requestData = {
url,
method: 'GET',
data: {
oauth_callback: CALLBACK_URL
}
};
// #ts-ignore
const authorisedRequest = await oauth.authorizeAsync(requestData, token);
let params = new URLSearchParams();
for (let [key, value] of Object.entries(authorisedRequest)) {
params.append(key, value as string);
}
const response = await fetch(requestData.url + `?${params}`, {
method: requestData.method,
});
const body = await response.text();
const parsedBody = oauth.deParam(body);
return parsedBody;
}

How to send queryStringParameters with invokeApi command

The full path to the endpoint with the query string parameters is:
https://api.mydomain.com/getData?param_01=value_01&param_02=value_01
After importing the 'aws-api-gateway-client'
var apigClientFactory = require('aws-api-gateway-client').default;
I go ahead and configure the variables:
let url = 'https://api.mydomain.com'
let pathTemplate = '/getData?param_01=value_01&param_02=value_01';
let method = 'GET';
let params = '';
let additionalParams = '';
let body = '';
var client = apigClientFactory.newClient({
invokeUrl: url,
accessKey: 'my-accessKeyId',
secretKey: 'my-secretAccessKey',
sessionToken: 'my-sessionToken',
region: 'MY_AWS_REGION'
});
Next invoke endpoint with:
client
.invokeApi(params, pathTemplate, method, additionalParams, body)
.then(function(res) {
console.log("...res:", res);
})
.catch(function(err) {
console.log("...err:", err);
});
But it fails with the error
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details
Is there a way to send the queryStringParameters with invokeApi command?
let params = {};
let pathTemplate = '/getData';
let additionalParams = {
queryParams: {
param0: 'value0',
param1: 'value1'
}
};
aws-api-gateway-client - npm

“Unauthorized” response when cityiq API

I am trying to access the Current powered by GE CityIQ API to develop a parking app, I followed the API documentation however I cannot seem to successfully query because I do not have an access token. I have a user name and password as well as the urls and predix zone id for parking provided by the city I am using. When I try and run my javascript and log my access token the response is “Unauthorized”. Do i have to raise a request to the city for the access token?
The code is written in javascript and is using node.js and node-fetch.
Here is my code:
const fetch = require("node-fetch")
function request(url, headers, body) {
let options = { headers: headers, body:body}
return fetch(url, options).then(result => {
if (result.status>=400) return(result.statusText)
else return result.text().then(txt => {
try { return JSON.parse(txt) }
catch (err) { return txt }
})
})
}
// my credentials
const developer, uaa, metadataservice, eventservice, predixZone
developer = '{user}:{pass}'
uaa='{uaaURL}'
eventservice='{eventURL}'
metadataservice='{metadataURL}'
predixZone='{predixzoneParking}'
async function example(event){
let devToken = (await request(uaa+'?grant_type=client_credentials', {authorization: 'Basic '+developer}))
console.log(devToken)
let output = (await request(metadataservice+'/assets/search?q=eventTypes:PKIN',{authorization: 'Bearer '+devToken,'predix-zone-id':predixZone})).content
console.log(output)
}
example()
What am I doing wrong or probably missing?
It looks like you have not base64 encoded your username and password.
At the top of your code:
const btoa = str => new Buffer(str).toString('base64')
When you declare your user name and pass:
developer = btoa('{user}:{pass}')

Apple IAP Receipt Validation Node.js

I'm trying to use Node.js from here for my IAP Receipt Validation, but it always returns error on a server's log: "The data in the receipt-data property was malformed."
Can someone help me to properly send base64 string to Node.js and to decode it there as same base64 string for receipt validation? I have zero experience with javascript and been trying to make this simple code work for two days now, to no avail.
Here is my Swift code:
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
var request = URLRequest(url: URL(string: "https://us-central1-calendizer-6a849.cloudfunctions.net/receiptValidation")!)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
// attach receipt data to a request
request.httpBody = receiptString.data(using: .utf8)
print("httpBody: \(request.httpBody?.base64EncodedString())")
Logs.log("✉️ Receipt Validation -> sending request ...")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
...
}
Here is my Node.js code (Firebase Cloud Functions):
const functions = require('firebase-functions');
var iap = require('in-app-purchase');
exports.receiptValidation = functions.https.onRequest((request, response) => {
var receipt_try1 = request.body
var receipt_try2 = request.body.toString('base64');
var receipt_try3 = JSON.stringify(receipt);
var receipt_try4 = new Buffer(request.body.toString(), 'base64')
console.log("receipt: " + receipt_try1)
iap.config({
applePassword: 'my shared key',
test: true
});
iap.setup(function (error) {
if (error) {
console.log("Setup error:" + error) // Failed to validate
}
iap.validate(iap.APPLE, receipt_try1, function (error, appleResponse) {
if (error)
{
console.log("Validation error:" + error) // Failed to validate
}
if (iap.isValidated(appleResponse)) {
console.log("successful validation" + appleResponse)
response.send(appleResponse)
}
});
});
});
did it using json:
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptBase64String = receiptData.base64EncodedString(options: [])
//.replacingOccurrences(of: "+", with: "%2B")
// prepare json data
let json: [String: Any] = ["receipt_string": receiptBase64String]
let jsonData = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
// create post request
var request = URLRequest(url: URL(string: "https://us-central1-calendizer-6a849.cloudfunctions.net/receiptValidation")!)
//request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = jsonData
and in Node.js simply:
var receipt = request.body.receipt_string

Categories