Guide to authorizing a POST request using AWS Signature Version 4

My approach to the majority of the code was inspired by a Python example from AWS, which I adapted for JS/node.

import axios from 'axios'
import {createHash, createHmac} from 'crypto'
import moment from 'moment'    
    
async function send() {
    
    // Code implementation here

   
}

However, when using this method in the browser, I encountered a 403: MissingAuthenticationToken error. Here is an overview of the request:

POST https://fjakldfda.execute-api.us-east-2.amazonaws.com/prod/events/add-event

HEADERS:
  Host: fjakldfda.execute-api.us-east-2.amazonaws.com
  User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0
  Accept: application/json, text/plain, */*
  Accept-Language: en-US,en;q=0.5
    
   // Rest of headers and data ...

 

The request works fine in Postman, but not in the browser, indicating that there might be an issue with how I hash or encode the signed headers.

In summary, what is the correct way to hash/sign a post request using AWS4?

Answer №1

Successfully figured it out! Decided to switch over to using crypto-js library and that did the trick.

import moment from 'moment'
import crypto from 'crypto-js'
import axios, { CancelToken } from "axios"   
    
async function send() {         
    const access_key = "XXXX"
    const secret_key = "XXXX"
    const method = 'POST';
    const service = 'execute-api';
    const host = 'dfadfadfdafda.execute-api.us-east-2.amazonaws.com';
    const region = 'us-east-2';
    const base = "https://"
    const content_type = 'application/json';

    // DynamoDB requires an x-amz-target header that has this format:
    //     DynamoDB_<API version>.<operationName>
    const amz_target = '';

    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;
    }

    // ************* TASK 1: CREATE A CANONICAL REQUEST *************
    // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

    // Step 1 is to define the verb (GET, POST, etc.)--already done.

    // Step 2: Create canonical URI--the part of the URI from domain to query 
    // string (use '/' if no path)
    // Create a date for headers and the credential string
    const amz_date = moment().utc().format("yyyyMMDDTHHmmss\\Z")
    const date_stamp =  moment().utc().format("yyyyMMDD")

    //// Step 3: Create the canonical query string. In this example, request
    // parameters are passed in the body of the request and the query string
    // is blank.
    const canonical_querystring = ''

    //## DOing step 6 first so that I can include the payload hash in the cannonical header, per https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
    // Step 6: Create payload hash. In this example, the payload (body of
    // the request) contains the request parameters.
    //const payload_hash = hashlib.sha256(request_parameters.encode('utf-8')).hexdigest()
    const payload_hash = crypto.SHA256(request_parameters);

    // Step 4: Create the canonical headers. Header names must be trimmed
    // and lowercase, and sorted in code point order from low to high.
    // Note that there is a trailing \n.
    const canonical_headers = 'host:' + host + '\n' + 'x-amz-content-sha256:' + payload_hash + '\n' + 'x-amz-date:' + amz_date + '\n'
    
    // Step 5: Create the list of signed headers. This lists the headers
    // in the canonical_headers list, delimited with ";" and in alpha order.
    // Note: The request can include any headers; canonical_headers and
    // signed_headers include those that you want to be included in the
    // hash of the request. "Host" and "x-amz-date" are always required.
    const signed_headers = 'host;x-amz-content-sha256;x-amz-date'

    // Step 7: Combine elements to create canonical request
    const canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash

    // ************* TASK 2: CREATE THE STRING TO SIGN*************
    // Match the algorithm to the hashing algorithm you use, either SHA-1 or
    // SHA-256 (recommended)
    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);

    // ************* TASK 3: CALCULATE THE SIGNATURE *************
    // Create the signing key using the function defined above.
    const signing_key = getSignatureKey(secret_key, date_stamp, region, service)

    // Sign the string_to_sign using the signing_key
    const signature = crypto.HmacSHA256(string_to_sign, signing_key);
    // ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
    // Put the signature information in a header named Authorization.
    const authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' +  'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature

    // For DynamoDB, the request can include any headers, but MUST include "host", "x-amz-date",
    // "x-amz-target", "content-type", and "Authorization". Except for the authorization
    // header, the headers must be included in the canonical_headers and signed_headers values, as
    // noted earlier. Order here is not significant.
    const headers = {
        'X-Amz-Content-Sha256':payload_hash, 
        'X-Amz-Date':amz_date,
        //'X-Amz-Target':amz_target,
        'Authorization':authorization_header,
        'Content-Type':content_type
    }

    // ************* SEND THE REQUEST *************
    var response = await axios({
        method: method,
        baseURL: base + host, 
        url: canonical_uri,
        data:request_parameters,
        headers: headers,
    });
    console.log(response)
}

Answer №2

Addressed a challenge using the integrated crypto module to resolve the issue. Implemented Buffer.from to maintain the key as binary data.

const crypto = require('crypto');

const { 
       method = 'GET',
       uri, 
       service, 
       region , 
       keyID, 
       secret,
       algo = 'AWS4-HMAC-SHA256',
       body = '',
       headers,
       jsonQuery, // a JSON object representing the query parameter of the call
      } = inputs

// Remaining code remains the same...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The direction to the Excel document for conversion into JSON

I have a project in progress where I'm currently working on converting an Excel sheet to JSON. Once the data is converted, it will be displayed using jQuery Datatables on the browser. My code is functioning as expected, but I am encountering an issue ...

Mongoose: An unexpected error has occurred

Recently, I developed an express app with a nested app called users using Typescript. The structure of my app.js file is as follows: ///<reference path='d.ts/DefinitelyTyped/node/node.d.ts' /> ///<reference path='d.ts/DefinitelyTyp ...

Trigger the change of an element upon hovering over another element

Is there a way to manipulate an element when another element is being hovered over, with the two elements structured like this: <div id="parent_element"> <div id="class-open-1"></div> <div id="class-close-1"></div> < ...

Having trouble mapping my JSON to a Spring Boot @RequestBody POJO

I'm struggling with an issue where I have a hard-coded JSON object that is supposed to map to my POJO in Spring Controller, but I keep getting null values. I have double-checked my getters and setters, and everything seems to be correct. Can anyone he ...

A guide on implementing arrow links in D3.js

i am struggling to add an arrow to one end of a link in my code. even though the links are functioning correctly, i can't seem to figure out how to draw arrows successfully. any assistance on how to implement this would be greatly appreciated. thank y ...

Tips for updating the values of 'Sec-Fetch-Dest', 'Sec-Fetch-Mode', 'Sec-Fetch-Site', and 'Sec-Fetch-User' using Axios

I'm having trouble figuring out how to set specific values for the 'Sec-Fetch-Dest', 'Sec-Fetch-Mode', 'Sec-Fetch-Site', and 'Sec-Fetch-User' headers when making a request through an axios instance in Vue. Unfo ...

Create a streaming service that allows for multicasting without prematurely ending the main subject

In my implementation of caching, I am utilizing BehaviorSubject and multicast. The cache stream should begin with an HTTP request and I want the ability to manually trigger a cache refresh by calling next on the subject. While the conventional method of us ...

Most efficient way to use JavaScript to filter a checkboxlist

Our project does not require the use of any javascript libraries such as jQuery, Dojo, or Prototype so finding a solution may be more challenging. I am looking for detailed answers to this question explaining how it can be accomplished. As many of you may ...

Encountering an issue when attempting to execute a sample test using Selenium WebDriver (WebdriverJS)

I attempted to execute a sample test in the file google_search_test.js found at \node_modules\selenium-webdriver\example. I am utilizing WebdriverJS and have only installed the selenium-webdriver NPM package on my system. Navigating to that ...

How can I efficiently locate identical sequences of cells in two or more arrays?

Unique Example 1 We can explore an interesting scenario by considering two arrays: ('m','o','o','n','s','t','a','r','d') ('s','t','a', ...

Retrieving a Date object from a JSON response

Hey there, I have a situation where I'm trying to convert a JSON response into a date object in order to display it in a different format. Here's the JSON response I'm working with: responseData: [{"id":10,"createdDate":"1 Sep, 2014 12:48:5 ...

Encountering a problem while attempting to execute the command "npm install -g http-server" on a Mac device

I am a complete beginner in the world of programming and feeling lost. Currently, I am taking a course called "Your Last Intro to Programming Course" on Udemy for a local coding bootcamp. Unfortunately, I am stuck at the setup stage and encountered an is ...

How can I prevent a hyperlinked element from being clicked again after it has been clicked using JavaScript or jQuery in PHP

I am struggling with disabling the href after it has been clicked. Can someone please assist me with this? It is crucial for me to complete this PHP program. Style.css .disabled { pointer-events: none; } ...

Tips for adjusting the button color in Material UI by utilizing the ":active" pseudo-class

In the project I am currently working on, I am incorporating Material UI. One of the tasks I need to complete is changing the active state of a button. I have implemented both hover and active states from Material UI in my code: const useStyles = makeStyle ...

Running a script within an Ajax-rendered form using remote functionality in a Ruby on Rails

After clicking the following button, my form is rendered: = link_to 'New Note', new_note_path, :class => "btn btn-primary new-note-button", :type => "button", :id => "new-link", remote: true The form is rendered using the script below ...

Why opt for ref.current over directly applying the condition?

I'm curious as to why the code uses if (ref.current && !ref.current.contains(event.target)) rather than if (!ref.current.contains(event.target) function useOutsideAlerter(ref) { useEffect(() => { // Function for click event function hand ...

Conditionally display a button utilizing V-IF when a Firestore value evaluates to true

I am fetching data from my Firestore database and displaying them on cards. I want to add buttons to the card only if a specific value in the loaded object is true. { "Alarm": false, "Safety": true, "Battery": true }. For example, if the values are as ab ...

Issue encountered while configuring 'innerHTML' in xmlHttp.onreadystatechange function

Trying to create a JavaScript function that changes the innerHTML of a paragraph within an xmlHttp.onreadystatechange function, I encountered an error in the Chrome Console: Uncaught TypeError: Cannot set property 'innerHTML' of null at XMLH ...

Jquery not functioning properly for email validation

I've been working on an email validation function, but for some reason it's not working as expected. Do you have any suggestions on what might be causing the issue? Am I missing something in my code or should I consider restructuring it? This i ...

What could be causing the timeout issue when trying to fetch guild members?

Hey there! I need some help with my discord bot. I'm trying to get a list of members ordered by their ids, but when I run the code below, all it does is log "Couldn't fetch members" without any other errors showing up. After digging deeper, I re ...