Hi!
I have an AppSync API and I am tried of using the AppSync console because it is quite lacking in features and it is VERY buggy.
I am happy to have found Apollo Studio! Pretty awesome so far.
My API has 3 auth modes:
- IAM (admin all access)
- API Key (public access)
- OIDC (Users JWTs)
API Key and OIDC work great out of the box. But I am having trouble with IAM auth.
I found out about Preflight scripts and thought it would be the perfect place to sign my requests and create the relevant header data needed to authorize my requests. I noticed it even comes with CryptoJS! Perfect!
I wrote a small script in node to test out the mechanisms to sign my query with crypto-js and was successful.
However, when I ported that code into the pre-flight script, I was not able to query my API. AWS tells me there is something wrong my signature.
I thought someone might help me out here.
Here is my node code that works:
const crypto = require("crypto-js");
const moment = require("moment");
const axios = require("axios");
const apiUrl =
"https://<MY_API>.appsync-api.ca-central-1.amazonaws.com/graphql";
const endpoint = new URL(apiUrl).hostname.toString();
const request = `mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
}
}`;
async function send() {
const access_key = "";
const secret_key = "";
const method = "POST";
const service = "appsync";
const host = endpoint;
const region = "ca-central-1";
const base = "https://";
const content_type = "application/json";
const canonical_uri = "/graphql";
function getSignatureKey(key, dateStamp, regionName, serviceName) {
var kDate = crypto.HmacSHA256(dateStamp, "AWS4" + key);
var kRegion = crypto.HmacSHA256(regionName, kDate);
var kService = crypto.HmacSHA256(serviceName, kRegion);
var kSigning = crypto.HmacSHA256("aws4_request", kService);
return kSigning;
}
const amz_date = moment().utc().format("yyyyMMDDTHHmmss") + "Z";
const date_stamp = moment().utc().format("yyyyMMDD");
const canonical_querystring = "";
const request_parameters = JSON.stringify({
query: request,
variables: { input: {} },
});
const payload_hash = crypto.SHA256(request_parameters);
const canonical_headers =
":authority:" +
host +
"\n" +
"x-amz-content-sha256:" +
payload_hash +
"\n" +
"x-amz-date:" +
amz_date +
"\n";
const signed_headers = ":authority;x-amz-content-sha256;x-amz-date";
const fs = require("fs");
const canonical_request =
method +
"\n" +
canonical_uri +
"\n" +
canonical_querystring +
"\n" +
canonical_headers +
"\n" +
signed_headers +
"\n" +
payload_hash;
fs.writeFileSync("canonical_request.txt", canonical_request);
const algorithm = "AWS4-HMAC-SHA256";
const credential_scope =
date_stamp + "/" + region + "/" + service + "/" + "aws4_request";
const string_to_sign =
algorithm +
"\n" +
amz_date +
"\n" +
credential_scope +
"\n" +
crypto.SHA256(canonical_request);
console.log(string_to_sign);
const signing_key = getSignatureKey(secret_key, date_stamp, region, service);
const signature = crypto.HmacSHA256(string_to_sign, signing_key);
const authorization_header =
algorithm +
" " +
"Credential=" +
access_key +
"/" +
credential_scope +
", " +
"SignedHeaders=" +
signed_headers +
", " +
"Signature=" +
signature;
const headers = {
"X-Amz-Content-Sha256": payload_hash.toString(),
"X-Amz-Date": amz_date,
Authorization: authorization_header,
"Content-Type": content_type,
};
const response = await axios({
method: method,
baseURL: base + host,
url: canonical_uri,
data: request_parameters,
headers: headers,
});
}
send()
My ported pre-flight script:
const crypto = explorer.CryptoJS
function getAmzDate() {
const utcYear = new Date().getUTCFullYear()
// const utcMonth = new Date().getUTCMonth()
const utcMonth = 11
const utcDay = new Date().getUTCDate()
const utcHour = new Date().getUTCHours()
const utcMinutes = new Date().getUTCMinutes()
const utcSeconds = new Date().getUTCSeconds()
const amz_date_v = `${utcYear}${utcMonth}${utcDay}T${utcHour}${utcMinutes}${utcSeconds}Z`
return amz_date_v
}
function setRequestHeaders() {
const access_key = "";
const secret_key = ""
const method = "POST";
const service = "appsync";
const host = "<MY_API>.ca-central-1.amazonaws.com";
const region = "ca-central-1";
const canonical_uri = "/graphql";
function getSignatureKey(key, dateStamp, regionName, serviceName) {
console.log(key, dateStamp, regionName, serviceName)
var kDate = crypto.HmacSHA256(dateStamp, "AWS4" + key);
var kRegion = crypto.HmacSHA256(regionName, kDate);
var kService = crypto.HmacSHA256(serviceName, kRegion);
var kSigning = crypto.HmacSHA256("aws4_request", kService);
return kSigning;
}
const amz_date = "20221111T010829Z" //temp until getAmzDate is fixed
const date_stamp = "20221111"
const canonical_querystring = "";
const request_parameters = JSON.stringify({
query: explorer.request.body.query,
variables: explorer.request.body.variables,
});
const payload_hash = crypto.SHA256(request_parameters);
const canonical_headers =
":authority" +
host +
"\n" +
"x-amz-content-sha256:" +
payload_hash +
"\n" +
"x-amz-date:" +
amz_date +
"\n";
const signed_headers = ":authority;x-amz-content-sha256;x-amz-date";
const canonical_request =
method +
"\n" +
canonical_uri +
"\n" +
canonical_querystring +
"\n" +
canonical_headers +
"\n" +
signed_headers +
"\n" +
payload_hash;
const algorithm = "AWS4-HMAC-SHA256";
const credential_scope =
date_stamp + "/" + region + "/" + service + "/" + "aws4_request";
const string_to_sign =
algorithm +
"\n" +
amz_date +
"\n" +
credential_scope +
"\n" +
crypto.SHA256(canonical_request);
const signing_key = getSignatureKey(secret_key, date_stamp, region, service);
const signature = crypto.HmacSHA256(string_to_sign, signing_key);
const authorization_header =
algorithm +
" " +
"Credential=" +
access_key +
"/" +
credential_scope +
", " +
"SignedHeaders=" +
signed_headers +
", " +
"Signature=" +
signature;
explorer.environment.set('authorization', authorization_header)
explorer.environment.set('date', amz_date)
explorer.environment.set('sha', payload_hash.toString())
}
setRequestHeaders()