mhart / aws4

Signs and prepares Node.js requests using AWS Signature Version 4
MIT License
699 stars 175 forks source link

Getting pre-signed URL for later use (e.g. for browsers) #130

Open Envek opened 3 years ago

Envek commented 3 years ago

One not so common but pretty handy use-case for this library is not modifying requests to be done right now, but generating pre-signed links that contains parameters and signing information using the query string. Such URL's can be passed via API to ither systems or even end-user browsers so they can access some authenticated API handles without any AWS credentials. See Signing AWS API requests / Using GET with authentication information in the Query string for reference.

I couldn't able to find ready to use javascript solution for generation of such pre-signed links with AWS signature version 4 and desperately started copy-pasting code from aws4 source to do all the low-level crypto by myself, but during that found signQuery option that almost does what I need. While this option is documented, I completely missed it when searched for ready solutions.

And here is the code that took so long to figure out. Hope it will help others to quickly accomplish similar tasks:

import { URL } from "url"
import AWS from "aws-sdk"
import aws4 from "aws4"

// Definition
export function signUrl(rawUrl: URL | string, options: any): string {
  const url = rawUrl instanceof URL ? rawUrl : new URL(rawUrl)

  const result = aws4.sign(
    {
      host: url.host,
      path: url.pathname + url.search,
      ...options,
      signQuery: true,
    },
    AWS.config.credentials,
  )

  return new URL(result.path, url).toString()
}

// Usage example
signUrl("https://some-api-gateway-custom-domain.com/path?foo=bar&baz=qux", { service: "execute-api" })

WDYT, does it make sense to create a pull request which adds such convenience function into aws4 itself?

febg11 commented 3 years ago

I am trying to generate presigned URLs in the browser that have a start/signed time of tomorrow with a 1 hour expire. I was using aws4fetch, can this easily be applied to that library?

Envek commented 3 years ago

@febg11, according to this library source code you can provide custom signing time in X-Amz-Date header: https://github.com/mhart/aws4/blob/a413aadd9e4b4e58842937a9ad53354be41ef4a1/aws4.js#L133-L136

Custom expiration time can be provided in X-Amz-Expires header: https://github.com/mhart/aws4/blob/a413aadd9e4b4e58842937a9ad53354be41ef4a1/aws4.js#L130-L131

But note that only S3 obeys to expiration header, and it looks like that pre-signed link expiration can't be overriden for other Amazon APIs, so links will be valid only for 5-15 minutes, depending on particular API.

florianbepunkt commented 3 years ago

@Envek Thank you, this is really helpful. How would you set a content type with the url?

I'm trying to implement this with a CSV file that is returned by API gateway. I can create a presigned link, but if I open it in a browser in returns base64 data. If I open the link in postman and set Accept header to text/csv I get the correct CSV data

Envek commented 3 years ago

As far as I know there is no such possibility.

You need to workaround this in your application and your API gateway. Possibly workarounds are:

florianbepunkt commented 3 years ago

@Envek Thank you. I found a workaround. Browsers normally send acomplex Accept header. API gateway uses the first mime type of the accept header that matches your specified binary types and converts the response. Since I do not use text/html in my api, it worked adding it to the binary media types.