How do I form an SAS token for Microsoft Azure API Management's REST API in Node.js? - node.js

I am using Microsoft Azure API Management service and want to use the REST API service. In creating my SAS token, which is needed otherwise the API call doesn't authorize, I'm having difficulty forming a proper token. Microsoft's webpage about this SAS token for API Management only shows an example in C#. I want to know how to form an SAS token in Node.js, which is not shown. Below is my code that was working last week, but is not now for some unknown reason. The error I get is: 401 Authorization error, token invalid
If someone can help me formulate this token, I would appreciate it.
This is Microsoft's webpage regarding this authentication token: https://learn.microsoft.com/en-us/rest/api/apimanagement/apimanagementrest/azure-api-management-rest-api-authentication
Here's my code:
const crypto = require('crypto');
const util = require('util');
const sign = () => {
const id = ${process.env.id}
const key = `${process.env.SASKey}`;
const date = new Date();
const newDate = new Date(date.setTime(date.getTime() + 8 * 86400000));
const expiry = `${newDate.getFullYear()}${
newDate.getMonth() < 10
? '' + newDate.getMonth() + 1
: newDate.getMonth() + 1
}${newDate.getDate()}${newDate.getHours()}${
newDate.getMinutes() < 10
? '0' + newDate.getMinutes()
: newDate.getMinutes()
}`;
const dataToSignString = '%s\n%s';
const dataToSign = util.format(dataToSignString, ${id}, expiry);
const hash = crypto
.createHmac('sha512', key)
.update(dataToSign)
.digest('base64');
const encodedToken = `SharedAccessSignature ${id}&${expiry}&${hash}`;
console.log(encodedToken);
return encodedToken;
};

Try the code:
protected getAPIManagementSAS(){
let utf8 = require("utf8")
let crypto= require("crypto")
let identifier = process.env.API_IDENTIFIER;
let key = process.env.API_KEY;
var now = new Date;
var utcDate = new Date(now.getUTCFullYear(),now.getUTCMonth(), now.getUTCDate() , now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds(), now.getUTCMilliseconds());
let expiry = addMinutes(utcDate,1,"yyyy-MM-ddThh:mm:ss") + '.0000000Z'
var dataToSign = identifier + "\n" + expiry;
var signatureUTF8 = utf8.encode(key);
var signature = crypto.createHmac('sha512', signatureUTF8).update(dataToSign).digest('base64');
var encodedToken = `SharedAccessSignature uid=${identifier}&ex=${expiry}&sn=${signature}`;
return encodedToken
}
For more information, see here.

After a million tries, it seems like the only format acceptable right now is:
SharedAccessSignature uid=${identifier}&ex=${expiry}&sn=${signature}
If you are using the other format that has the "integration" parameter, that's a hit or a miss, mostly miss though. Set the uid as "integration" if that's your identifier and follow the above format as it works.

Related

Converting .NodeJS Authentication Token script to Apps Script

I am a newer Apps Scripts user who has been stuck on this problem for some time. I am trying to connect my company's reporting feature to a Google Sheet so I can create reporting dashboards for our users.
I have a .jsNode code snippet which I am able to successfully run outside of the Apps Scripts editor, but I am having issues translating it to Apps Scripts.
My goal is to be able to generate an Authentication Token which will be used in a header within a POST request. I will then use this to get a specific URL that I can pull data from (it will be a .csv file. I already feel I can accomplish this using another script)
Below is the .NodeJS code snippet:
const crypto = require('crypto');
module.exports.init = function () {
let prefix = 'abc-admin-v1'
let businessId = 'XXXX'
let publicAppKeyId = 'XXXX'
let secretKey = 'XXXX'
let unixTimestamp = Math.floor(Date.now() / 1000)
let payload = `${prefix}${businessId}${unixTimestamp}`
let rawKey = Buffer.from(secretKey, 'base64')
let signature = crypto.createHmac('sha256', rawKey).update(payload, 'utf8').digest('base64')
let token = `${signature}${payload}`
let httpBasicPayload = `${publicAppKeyId}:${token}`
let httpBasicCredentials = Buffer.from(httpBasicPayload, 'utf8').toString('base64')
console.log(httpBasicCredentials);
};
Below will be what I have in Apps Scripts:
function apiInfo() {
var secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var apiKey = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
var locationID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
var prefix = "abc-admin-v1";
var timeStamp = Math.round((new Date()).getTime() / 1000);
var timeStampReal = JSON.stringify(timeStamp);
var tokenPayload = prefix + businessID + timeStampReal;
var enCodeTokenPayload = Utilities.base64Encode(tokenPayload);
var deCodeTokenPayload = Utilities.base64Decode(enCodeTokenPayload);
var tokenPayloadReal = JSON.stringify(tokenPayload);
var rawKey = Utilities.base64Decode(secret);
var rawMac = Utilities.computeHmacSha256Signature(tokenPayloadReal, rawKey);
var signature = Utilities.base64Encode(rawMac);
var token = signature + tokenPayload;
var httpBasicPayload = apiKey + ":" + token;
var httpBasicCredentials = Utilities.base64Encode(httpBasicPayload);
When testing using the current GAS code, it produces an invalid Auth Token.
When I run the Node.js version outside of GAS, it produces a valid Auth Token. I am looking to find out what is currently wrong with my code that is producing an invalid auth token. I would like to keep the project within GAS if possible.
I believe your goal is as follows.
You want to convert your showing Node.js script to Google Apps Script. Namely, you want to retrieve the same values of httpBasicCredentials between your Node.js script and Google Apps Script.
const crypto = require('crypto');
module.exports.init = function () {
let prefix = 'abc-admin-v1'
let businessId = 'XXXX'
let publicAppKeyId = 'XXXX'
let secretKey = 'XXXX'
let unixTimestamp = Math.floor(Date.now() / 1000)
let payload = `${prefix}${businessId}${unixTimestamp}`
let rawKey = Buffer.from(secretKey, 'base64')
let signature = crypto.createHmac('sha256', rawKey).update(payload, 'utf8').digest('base64')
let token = `${signature}${payload}`
let httpBasicPayload = `${publicAppKeyId}:${token}`
let httpBasicCredentials = Buffer.from(httpBasicPayload, 'utf8').toString('base64')
console.log(httpBasicCredentials);
};
When I saw your showing Google Apps Script, unfortunately, there are several undeclared variable and unused variables. By this, in this answer, I would like to propose to directly converting your showing Node.js to Google Apps Script.
Sample script:
function myFunction() {
let prefix = 'abc-admin-v1'
let businessId = 'XXXX'
let publicAppKeyId = 'XXXX'
let secretKey = 'XXXX'
let unixTimestamp = Math.floor(Date.now() / 1000);
let payload = `${prefix}${businessId}${unixTimestamp}`;
let payloadBytes = Utilities.newBlob(payload).getBytes();
let rawKey = Utilities.base64Decode(secretKey);
let signature = Utilities.base64Encode(Utilities.computeHmacSha256Signature(payloadBytes, rawKey));
let token = `${signature}${payload}`;
let httpBasicPayload = `${publicAppKeyId}:${token}`;
let httpBasicCredentials = Utilities.base64Encode(httpBasicPayload);
console.log(httpBasicCredentials);
}
Testing:
In the above scripts, when the value of unixTimestamp is 1661403828, both your Node.js and my sample Google Apps Script are the same values as follows.
WFhYWDpGYXBwRk9kaFoxdDVQNGVkV2JJbFIvR1RiMkEyZ0hwYThld05BYjVDVkxnPWFiYy1hZG1pbi12MVhYWFgxNjYxNDAzODI4
Note:
From your question, in this answer, your Node.js is converted to Google Apps Script. Please be careful about this.
References:
base64Encode(data)
computeHmacSha256Signature(value, key)

Is it possible to create JWT tokens using key vault keys?

i want to us Key Vault key to create JWT token and then validate it.
Im using this code:
public static async Task<string> SignJwt()
{
var tokenHandler = new JwtSecurityTokenHandler();
var signinKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", "1") }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(signinKey, SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
and it works fine. I was googling a lot and found this snippet for SigningCredentials using Identity extension nuget:
new SigningCredentials(new KeyVaultSecurityKey("https://myvault.vault.azure.net/keys/mykey/keyid", new KeyVaultSecurityKey.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)), "RS256")
{
CryptoProviderFactory = new CryptoProviderFactory() { CustomCryptoProvider = new KeyVaultCryptoProvider() }
});
But it is not clear for me, what really AuthenticationCallback is and how to implement that and if i will be able to use that in Azure in web app or azure function?
Firstly, a JWT token consists of 3 parts (Header, Payload and Signature) and all those 3 parts are Base64UriEncoded.
To get the Signature you need to generate header and payload, then combine them by dot.**
Below is the sample code to verify JWT using Azure kay Vault.
const key = await this.keyClient.getKey(this.KEY_NAME);
const cryptClient = new CryptographyClient(key, new DefaultAzureCredential());
const util =require('util')
const base64 = require('base64url');
const JWT=""
const jwtHeader = JWT.split('.')[0];
const jwtPayload = JWT.split('.')[1];
const jwtSignature = JWT.split('.')[2];
const signature = base64.toBuffer(jwtSignature)
const data = util.format('%s.%s', jwtHeader, jwtPayload);
const hash = crypto.createHash('sha256');
const digest = hash.update(data).digest()
const verified =await cryptClient.verify("RS256",digest,signature)
Here are few SO threads with related discussions. SO1, SO2 and SO3

Build hmac sha256 hashing algorithm in node.js

I am using Bold Commerce Webhooks to subscribe to subscription events in my store. They give documentation about how their request signatures are generated in PHP:
$now = time(); // current unix timestamp
$json = json_encode($payload, JSON_FORCE_OBJECT);
$signature = hash_hmac('sha256', $now.'.'.$json, $signingKey);
I'm trying to recreate the hash on my side in node.js. From my research I've figured out the following so far, which I believe is pretty close, but doesn't match yet:
const hash = request.header("X-Bold-Signature")!;
const SECRET = "my-secret-api-key";
const body = request.body;
const time = request.header("timestamp")!;
const mySignature = crypto.createHmac('sha256', SECRET).update(time + '.' + body).digest("hex");
if (mySignature !== request.header("X-Bold-Signature")!) {
//...
}
I've also tried using JSON.stringify(body) which changes the hash but still doesn't match.
It matched with this code.
const hash = crypto
.createHmac('sha256', secretKey)
.update(timestamp + '.' + JSON.stringify(body))
.digest('hex');
Note that unlike shopify, do not use rawbody.

AWS Signature Version 4 S3 Upload using Node.js

I've been following the AWS example on how to generate a V4 HMAC signature. I've done this successfully in Java but I'm trying to get it to work in Node/JavaScript. When I use my code I generate all the correct intermediary keys in their 1st example below but on the next example when given the test StringToSign the same code that generated the correct intermediary keys fails to generate the supposed correct signature.
Correct Intermediary Keys:
secretkey = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20120215'
regionName = 'us-east-1'
serviceName = 'iam'
kSecret = '41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559'
kDate = '969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d'
kRegion = '69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c'
kService = 'f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa'
kSigning = 'f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d'
http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html.
Fails With the Following Input
secretkey = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20151229'
regionName = 'us-east-1'
serviceName = 's3'
StringToSign
eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLA0KICAiY29uZGl0aW9ucyI6IFsNCiAgICB7ImJ1Y2tldCI6ICJzaWd2NGV4YW1wbGVidWNrZXQifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwNCiAgICB7ImFjbCI6ICJwdWJsaWMtcmVhZCJ9LA0KICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tL3N1Y2Nlc3NmdWxfdXBsb2FkLmh0bWwifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAiaW1hZ2UvIl0sDQogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwNCiAgICB7IngtYW16LXNlcnZlci1zaWRlLWVuY3J5cHRpb24iOiAiQUVTMjU2In0sDQogICAgWyJzdGFydHMtd2l0aCIsICIkeC1hbXotbWV0YS10YWciLCAiIl0sDQoNCiAgICB7IngtYW16LWNyZWRlbnRpYWwiOiAiQUtJQUlPU0ZPRE5ON0VYQU1QTEUvMjAxNTEyMjkvdXMtZWFzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LA0KICAgIHsieC1hbXotYWxnb3JpdGhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwNCiAgICB7IngtYW16LWRhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfQ0KICBdDQp9
Correct Signature: 46503978d3596de22955b4b18d6dfb1d54e8c5958727d5bdcd02cc1119c60fc9
My Signature: e7318f0bfd7d86fb9ba81c314f62192ee2baf7273792ef01ffafeb430fc2fb68
http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
My Code
var crypto = require("crypto-js")
module.exports.getSignatureKey = function(key, dateStamp, regionName, serviceName) {
var kSecret = "AWS4" + key
var kDate = crypto.HmacSHA256(dateStamp, kSecret)
var kRegion = crypto.HmacSHA256(regionName, kDate)
var kService = crypto.HmacSHA256(serviceName, kRegion)
var kSigning = crypto.HmacSHA256("aws4_request", kService)
return kSigning;
}
module.exports.sign = function(signatureKey,stringToSign) {
var unencodedSignature = crypto.HmacSHA256(stringToSign,signatureKey)
return unencodedSignature
}
module.exports.getSignature = function(stringToSign,secretKey,dateStamp,regionName, serviceName) {
var signingKey = this.getSignatureKey(secretKey,dateStamp,regionName,serviceName)
return this.sign(signingKey,stringToSign)
}
The AWS example in the second link has the wrong signature. Using my solution I am able to successfully upload to s3.
Something else to consider is that the crypto-js node library outputs the signatures already in hex. Theres no need to do a manual conversion yourself as you would if you we in Java using the example code they provide.

Amazon S3 Signature

How do you create the hex encoded signature in crypto in a nodejs environment?
Do you just do this like so?
const secret = 'mysecret';
const date = yyyymmdd;
const dateKey = crypto.createHmac('sha256', 'AWS4' + secret + ',' + date);
const dateRegionKey = crypto('sha256', dateKey + ',' + 'myregion')
const DateRegionServiceKey = crypto('sha256', dateRegionKey + ',' + 'someservice');
const signingKey = crypto('sha256', DateRegionServiceKey + ',' + 'aws4_request');
const signature = crypo('sha256', signingKey + base64Policy);
High level it looks ok. I hope you know that secret is not just a string, it is the secretKey of your IAM User accessKey/secretKey pair.
Any particular reason you want to do it yourself and not use the AWS SDK?
Also look at below for a sample signing implementation (in Java) which works for AWS ElasticSearch. Look at getSignatureKey and calculateSignature methods.
https://github.com/dy10/aws-elasticsearch-query-java/blob/master/src/main/java/dy/aws/es/AWSV4Auth.java
you should use this library https://github.com/mhart/aws4
you need to use result = aws4.sign({}, { cred from AWS })
and it returns result.headers that are the headers you need to send in your request

Resources