Signing Requests

Get ready to learn how to sign requests to an AWS service using security keys!

IAM user keys #

To explain how temporary grants work, you first need to understand the role of the security keys you entered when configuring command-line access in Chapter 2.

Each IAM user has two keys:

  • an access key
  • a secret key

When the SDK makes a request to an AWS service, for example s3.putObject(), it sends the access key in the request headers. This allows the service to map the request to an AWS account. The SDK also sends a cryptographic signature based on the request body and the secret key using Amazon’s Signature Version 4 Signing Process (SIGV4 algorithm). The receiving service uses the access key to locate the corresponding secret key in the IAM database, and also creates a SIGV4 signature for the request. If the two signatures match, AWS knows that the request was authorised by the user.

The interesting part of this is that some services accept templated signatures, so you can create a grant upfront without knowing all the request parameters. This allows you to effectively produce temporary grants for users to perform limited operations with your AWS resources. For example, you could sign a request matching an S3 upload to a specific file key, in a specific bucket, up to a specific size, and only valid for a specific period of time. You can then safely send this signature to a client, without the risk of exposing the real secret key. The client code can try to upload a file and include the signature in the authorisation header. S3 will examine the signature, evaluate the request against the access policy, and allow or reject the request.

S3 will evaluate the signature both based on the restrictions in the temporary grant and the permissions assigned to the access key. The grant will be invalid if you sign the operation using a key that does not have access to the S3 bucket. This means that you can safely sign temporary grants from a Lambda function. A function can never pass on privileges that it does not have itself.

s3.createPresignedPost #

Using a Lambda function to just pre-sign uploads for client devices is a very common usage pattern. The AWS SDK for JavaScript already has a shortcut method for that operation, so you don’t have to learn the details of the SIGV4 signature process. The method is s3.createPresignedPost. It takes a map of conditions such as the expiration time of the policy, partial or full matching on the uploaded file key, maximum allowed length, and the default access level for the uploaded file. The function returns a list of fields that can be used to construct an HTML form.

There are two ways to use those fields. One is to create an HTML form directly and display it on a web page. Browsers will show a button to select a local file and post it to S3, and S3 will redirect the user to a specified page after a successful upload.

Alternatively, you can use those fields to create a FormData browser object and upload the file using a background process, such as browser HTML5 fetch or XMLHttpRequest. The second approach is a lot more flexible​ because you could use JavaScript to set the content type or target file name, and it would allow you to create a nicer user experience. But to keep things simple for now, you’ll use the first approach and just let the browser show a form.

With a browser form workflow, you can tell S3 to redirect users somewhere after a successful upload. You can set up an API endpoint to handle the redirects, and then provide the URL to the function signing the credentials. The redirect comes from S3, not your API, so you’ll need to define an absolute URL. You could pass the API ID and stage into a Lambda function as variables to create an absolute URL, but you don’t have to. When API Gateway sends a request using the Lambda proxy integration, it will also add a requestContext property with some operational information about the request itself.

requestContext #

Check one of the CloudWatch logs from the previous deployments to see this. You’ll see that it contains two interesting fields:

  • event.requestContext.domainName is the full domain name used by the client to make the request.
  • event.requestContext.stage is the API Gateway stage that received the request.

With API Gateway Lambda proxy integrations, the requestContext field contains all the contextual information about the API Gateway resource. This information does not come from the client devices, but from the API Gateway, so it’s more reliable than using request headers.

Using the request context, you can easily construct an absolute URL to some other resource on the same API stage. For example, you’ll set up a new function to confirm that the upload was successful on /confirm. To get the absolute URL, you need to use something like this:

Get hands-on with 1000+ tech skills courses.