Authenticating with AWS API Gateway with IAM credentials using Javascript and Node

I’ve recently had the requirement to build a secure backend API layer in AWS that can be accessed both internally by services in AWS, but also by developers locally. We took the view that having the developers use a VPN to connect to the VPC was not the best idea, and that we should be able to use AWS to handle the authentication.

In case you’re not familiar (not sure how you wouldn’t considering you made it to this post) but anyway…. AWS API Gateway provides the ability to use IAM permissions to access the API Gateway (https://docs.aws.amazon.com/apigateway/latest/developerguide/security_iam_service-with-iam.html), and therefore block the request from making it to your backend Lambda or Load Balancer. This is really useful and used a lot in Serverless where people are using things like AWS Cognito for authentication.

Unfortunately, having server-side code call the AWS API Gateway isn’t that easy. It requires signing of requests, and the AWS SDK’s don’t make that easy to access. The signatures are a mixture of the hashes of some of the data in the request, HMACs and encryption using your AccessKey/Secret.

In this post, I hope to provide a simple example of how to call those APIs from Javascript/Node.

Signed Requests

In order to call an API Gateway endpoint that is secured with IAM permissions, you’ll need to create a “signature” in the AWS Signature 4 format (https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).

Once you’ve created that signature, you can either append it to the querystring, or add it as a header. There will be multiple additional properties to add

  • X-Amz-Security-Token
  • X-Amz-Date
  • Authorization

The code

The code bit of code is using the aws4 npm package (https://github.com/mhart/aws4). This allows you to pass the credentials, and the pertinent parts of the request you want to make, and it will generate the right signatures in a headers object for you.

It’s an incredibly easy to use library, and I can’t thank Michael Hart enough for his efforts, give him a follow and, if you can, consider sponsoring his efforts.

Now, I’m not a javascript developer, so this may all be completely wrong, but I feel I can understand it, and reason about it, which was the goal, not a complete implementation in an application.

import fetch from 'node-fetch';
import aws4 from 'aws4';

const opts = {
    method: "GET",
    host: "",
    path: "",
    port: 0,
    service: 'execute-api',
    payload: "",
    region: 'eu-west-2'
};

aws4.sign(opts);
const response = await fetch("https://" + opts.host + opts.path, {
    headers: opts.headers
});

console.log(await response.json());

What this code does is build up an object that includes some important things that AWS signatures care about.

  • method of the request (GET, POST, PUT, DELETE, etc.)
  • host of the request (the domain name) Note that this works when using custom domains for AWS API Gateway too.
  • path the full path with querystring of the endpoint
  • port you’ll be calling if it’s non-default (if it’s default, omit it, or use 0)
  • service within AWS you’ll be calling. For AWS API Gateway you’ll need execute-api
  • payload for the body you’ll be passing
  • region of the API Gateway you’ll be hitting (this is required)

That object is then passed to aws4‘s sign method, which appends a headers object which includes the headers required, including a host header. By default this will use the credentials from your process.env, or you can pass them in. In a future post, I’ll expand this to include Assumed roles.

We can then attach these headers to the node-fetch method, and pass in the url. For this, I’ve chosen to hardcode https as, well, it’s 2022, and then take the properties from the opts object so everything maps nicely.

Finally, we can write the output from the API. If your signing hasn’t work, you’ll get a JSON object that says forbidden.

Conclusion

Finding all this information was quite hard, and as javascript isn’t my primary language, I found it hard to understand a few things. That said, I feel this is pretty neat solution and quite succinct. I hope this helps some people, and if you have feedback on the code, please do let me know.

You can find the full implementation as a Node program with Assumed roles here: https://gist.github.com/martinjt/98739421411ace776e84d53d331b2ddf

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Website Built with WordPress.com.

Up ↑

%d bloggers like this: