I am attempting to set up a Cloudwatch Synthetics Canary that can Query my API with a POST action to make an Authentication attempt, return a token then do a second request using that token in the header.
My Code for the multi step Canary looks like this (removed the bit at the top that talks to secrets manager as that's irrelevant for this question):
const synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
const syntheticsConfiguration = synthetics.getConfiguration();
const apiCanaryBlueprint = async function () {
const [ key, cert ] = await getKeyCert();
syntheticsConfiguration.setConfig({
restrictedHeaders: [], // Value of these headers will be redacted from logs and reports
restrictedUrlParameters: [] // Values of these url parameters will be redacted from logs and reports
});
// Handle validation for positive scenario
const validateSuccessful = async function(res) {
return new Promise((resolve, reject) => {
if (res.statusCode < 200 || res.statusCode > 299) {
throw new Error(res.statusCode + ' ' + res.statusMessage);
}
let responseBody = '';
res.on('data', (d) => {
responseBody += d;
});
res.on('end', () => {
// Add validation on 'responseBody' here if required.
resolve();
});
});
};
// Set request option for Verify mywebsite.com
let requestOptionsStep1 = {
hostname: 'mywebsite.com',
method: 'POST',
path: '/v1/oauth/token',
port: '443',
protocol: 'https:',
body: "{\n\"client_id\":\"xxx\",\n\"client_secret\":\"yyy\",\n\"audience\":\"https://mywebsite.com\",\n\"grant_type\":\"client_credentials\"\n}",
headers: {"Content-Type":"application/json"},
key: key,
cert: cert
};
requestOptionsStep1['headers']['User-Agent'] = [synthetics.getCanaryUserAgentString(), requestOptionsStep1['headers']['User-Agent']].join(' ');
// Set step config option for Verify mywebsite.com
let stepConfig1 = {
includeRequestHeaders: true,
includeResponseHeaders: true,
includeRequestBody: true,
includeResponseBody: true,
continueOnHttpStepFailure: true
};
await synthetics.executeHttpStep('Verify mywebsite.com', requestOptionsStep1, validateSuccessful, stepConfig1);
// Set request option for LP Lookup mywebsite.com-2
let requestOptionsStep2 = {
hostname: 'mywebsite.com',
method: 'GET',
path: '/my/api/request/path',
port: '443',
protocol: 'https:',
body: "",
headers: {"content-type":"application/json","authorization:":"bearer: VALUE FROM FIRST REQEST"}
};
requestOptionsStep2['headers']['User-Agent'] = [synthetics.getCanaryUserAgentString(), requestOptionsStep2['headers']['User-Agent']].join(' ');
// Set step config option for LP Lookup mywebsite.com-2
let stepConfig2 = {
includeRequestHeaders: true,
includeResponseHeaders: true,
includeRequestBody: true,
includeResponseBody: true,
continueOnHttpStepFailure: true
};
await synthetics.executeHttpStep('LP Lookup mywebsite.com-2', requestOptionsStep2, validateSuccessful, stepConfig2);
};
exports.handler = async () => {
return await apiCanaryBlueprint();
};
The response body from the first query looks like:
{"access_token": "MYTOKEN", "scope": "vds rc", "expires_in": 51719, "token_type": "Bearer"}
So I basically need to get the "MYTOKEN" value and use it in my second request where I have "VALUE FROM FIRST REQEST"
Thanks in Advance
I was able to answer my own question so sharing here in case it helps someone else, my final code looks like this:
const synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
const syntheticsConfiguration = synthetics.getConfiguration();
const apiCanaryBlueprint = async function () {
const [ key, cert, clientid, clientkey ] = await getSecrets();
syntheticsConfiguration.setConfig({
restrictedHeaders: [], // Value of these headers will be redacted from logs and reports
restrictedUrlParameters: [] // Values of these url parameters will be redacted from logs and reports
});
// Handle validation for positive scenario
const validateSuccessful = async function(res) {
return new Promise((resolve, reject) => {
if (res.statusCode < 200 || res.statusCode > 299) {
throw new Error(res.statusCode + ' ' + res.statusMessage);
}
global.responseBody = '';
res.on('data', (d) => {
responseBody += d;
});
res.on('end', () => {
// Add validation on 'responseBody' here if required.
resolve();
});
});
};
// Set request option for Verify mywebsite.com
let requestOptionsStep1 = {
hostname: process.env.MYWEBSITE_URL,
method: 'POST',
path: '/v1/oauth/token',
port: '443',
protocol: 'https:',
body: `{\n\"client_id\":\"${clientid}\",\n\"client_secret\":\"${clientkey}\",\n\"audience\":\"https://mywebsite.com\",\n\"grant_type\":\"client_credentials\"\n}`,
headers: {"Content-Type":"application/json"},
key: key,
cert: cert
};
requestOptionsStep1['headers']['User-Agent'] = [synthetics.getCanaryUserAgentString(), requestOptionsStep1['headers']['User-Agent']].join(' ');
// Set step config option for Verify mywebsite.com
let stepConfig1 = {
includeRequestHeaders: true,
includeResponseHeaders: true,
includeRequestBody: true,
includeResponseBody: true,
continueOnHttpStepFailure: true
};
await synthetics.executeHttpStep('Verify mywebsite.com', requestOptionsStep1, validateSuccessful, stepConfig1);
var jsonauthtoken = JSON.parse(responseBody)
const authToken = jsonauthtoken.access_token;
// Set request option for Verify mywebsite.com
let requestOptionsStep2 = {
hostname: process.env.MYWEBSITE_URL,
method: 'GET',
path: '/my/api/request/path',
port: '443',
protocol: 'https:',
headers: {"Content-Type":"application/json", "authorization": `Bearer ${authToken}`},
body: "",
key: key,
cert: cert
};
requestOptionsStep2['headers']['User-Agent'] = [synthetics.getCanaryUserAgentString(), requestOptionsStep2['headers']['User-Agent']].join(' ');
// Set step config option for Verify mywebsite.com
let stepConfig2 = {
includeRequestHeaders: true,
includeResponseHeaders: true,
includeRequestBody: true,
includeResponseBody: true,
continueOnHttpStepFailure: true
};
await synthetics.executeHttpStep('QUERY mywebsite.com', requestOptionsStep2, validateSuccessful, stepConfig2);
};
exports.handler = async () => {
return await apiCanaryBlueprint();
};
The most important things to note were:
When it executes the first step It is sending it's output up to the "validateSuccessful" section so in that section I set the content of the responsebody to be a global variable
global.responseBody = '';
Then I was able to access it before setting the options for step 2 like this and parse the JSON:
var jsonauthtoken = JSON.parse(responseBody)
const authToken = jsonauthtoken.access_token;
then I could use the auth token in my header for the second request:
headers: {"Content-Type":"application/json", "authorization": `Bearer ${authToken}`},
#note the backticks allow us to read the variable into our string
and that was it. My final example has some other changes where I made use of Environment variables instead of hard coding things, hence the changes to the body section of the first request and the hostname