aws4fetch

repository·master·Indexed 21 days ago

https://github.com/mhart/aws4fetch

A lightweight AWS Signature Version 4 client for modern JS environments supporting the Fetch API and SubtleCrypto, such as web browsers and Cloudflare Workers. It provides the AwsClient class for making signed requests via aws.fetch() and aws.sign(), as well as the AwsV4Signer class for low-level access to raw signature components.

Tokens
4.1K
Snippets
13
Records
13
Agent score
73%

What's inside aws4fetch

  1. Call AWS Lambda from Cloudflare Workers using aws4fetch

    master

    You can use aws4fetch in a Cloudflare Worker to call AWS Lambda functions directly, bypassing the need for an API Gateway. This involves initializing an AwsClient with your AWS credentials (stored as environment variables/secrets), constructing the Lambda invocation URL, and using aws.fetch to send a signed request.

    To ensure the Lambda receives the expected data, you must convert the incoming Cloudflare Request object into an API-Gateway-style Lambda event object containing httpMethod, path, queryStringParameters, headers, and body.

    import { AwsClient } from 'aws4fetch'
    
    // Initialize client with environment variables
    const aws = new AwsClient({
      accessKeyId: env.AWS_ACCESS_KEY_ID,
      secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
    })
    
    // Lambda invocation URL format
    const LAMBDA_INVOKE_URL = `https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/${LAMBDA_FN}/invocations`
    
    // Execute the signed fetch
    const lambdaResponse = await aws.fetch(LAMBDA_INVOKE_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(await toLambdaEvent(request)),
    })
  2. Handle Lambda Responses in Cloudflare Workers

    master

    After calling aws.fetch to invoke a Lambda, you must handle the response. If the invocation was successful, the Lambda typically returns a JSON object containing statusCode, headers, and body. You should then convert this back into a standard Web API Response to return to the client.

    const lambdaResponse = await aws.fetch(LAMBDA_INVOKE_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(await toLambdaEvent(request)),
    })
    
    if (!lambdaResponse.ok) {
      console.error(await lambdaResponse.text())
      return Response.json({ error: `Lambda API returned ${lambdaResponse.status}` }, { status: 500 })
    }
    
    const { statusCode: status, headers, body } = await lambdaResponse.json()
    
    return new Response(body, { status, headers })
  3. Convert a Request to a Lambda Event

    master

    When proxying requests from a Cloudflare Worker to AWS Lambda, use this pattern to transform a standard Web API Request into the JSON structure expected by an AWS Lambda function configured with API Gateway proxy integration.

    async function toLambdaEvent(request) {
      const url = new URL(request.url)
      return {
        httpMethod: request.method,
        path: url.pathname,
        queryStringParameters: Object.fromEntries([...url.searchParams]),
        headers: Object.fromEntries([...request.headers]),
        body: ['GET', 'HEAD'].includes(request.method) ? undefined : await request.text(),
      }
    }
  4. Use AwsV4Signer for raw signing

    master

    The AwsV4Signer class is the underlying signing engine. Use it if you need to work with the raw components of a signed request (method, URL, headers, body) without using the fetch abstraction.

    Constructor Options:

    • url (required): The AWS endpoint to sign.
    • accessKeyId (required): AWS access key ID.
    • secretAccessKey (required): AWS secret access key.
    • sessionToken: AWS session token.
    • method: HTTP method (defaults to POST if body is present, otherwise GET).
    • headers: Standard JS object or Headers instance.
    • body: String or ArrayBuffer/ArrayBufferView.
    • signQuery: Boolean. Sign query string instead of Authorization header.
    • service: AWS service.
    • region: AWS region.
    • cache: Credential cache.
    • datetime: Signing timestamp.
    • appendSessionToken: Boolean. Adds X-Amz-Security-Token (defaults to true).
    • allHeaders: Boolean. Force all headers to be signed.
    • singleEncode: Boolean. Single encoding for %2F.
    import { AwsV4Signer } from 'aws4fetch'
    
    const signer = new AwsV4Signer({
      url: 'https://example.amazonaws.com/path',
      accessKeyId: 'KEY',
      secretAccessKey: 'SECRET',
      method: 'GET'
    })
    
    async function sign() {
      // Returns { method, url, headers, body }
      const { method, url, headers, body } = await signer.sign()
      
      console.log(method, url, [...headers], body)
    }
  5. Create a signed Request with aws.sign()

    master

    The aws.sign(input[, init]) method returns a Promise that resolves to an AWS4-signed Request object. This is useful if you want to use the signed request with a different fetch implementation or custom logic.

    It accepts the same arguments as fetch and the same aws override options as aws.fetch().

    import { AwsClient } from 'aws4fetch'
    
    const aws = new AwsClient(opts)
    
    async function doFetch() {
      const request = await aws.sign('https://example.amazonaws.com/path', {
        method: 'GET',
        aws: { service: 's3' }
      })
    
      // Use the signed request with standard fetch
      const response = await fetch(request)
      return await response.json()
    }
  6. Make signed requests with aws.fetch()

    master

    The aws.fetch(input[, init]) method has the same signature as the standard Web fetch API. It automatically signs the request using AWS Signature Version 4.

    Performance Tip: It is faster to pass the URL as a string and the body in the init object rather than passing a Request object directly.

    The aws option in init: You can override AwsClient settings for a specific call by providing an aws object in the init argument:

    • signQuery: Boolean. If true, signs the query string instead of the Authorization header.
    • accessKeyId, secretAccessKey, sessionToken, service, region, cache: Override instance defaults.
    • datetime: Override the signing timestamp (format: '20150830T123600Z').
    • appendSessionToken: Boolean. Adds X-Amz-Security-Token after signing (defaults to true; required for IoT).
    • allHeaders: Boolean. If true, forces all headers to be signed.
    • singleEncode: Boolean. If true, only encodes %2F once (useful for testing).
    import { AwsClient } from 'aws4fetch'
    
    const aws = new AwsClient({ accessKeyId, secretAccessKey })
    
    async function doFetch() {
      const response = await aws.fetch('https://example.amazonaws.com/path', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ key: 'value' }),
        aws: {
          region: 'us-west-2',
          signQuery: true
        }
      })
      
      const data = await response.json()
      return data
    }
  7. Access raw signature components via AwsV4Signer

    master

    If you are constructing your own requests manually, AwsV4Signer provides methods to retrieve specific signed components:

    • signer.authHeader(): Returns a Promise resolving to the signed string for the Authorization header.
    • signer.signature(): Returns a Promise resolving to the hex signature.
    // Use these only if constructing custom requests manually
    const authHeader = await signer.authHeader()
    const signature = await signer.signature()
  8. Initialize an AwsClient

    master

    The AwsClient class is the primary interface for making signed AWS requests. You can reuse a single instance for multiple service calls because the service and region are typically parsed from the URL at fetch time. You can also provide them explicitly in the constructor or override them per request.

    Constructor Options:

    • accessKeyId (required): AWS access key ID.
    • secretAccessKey (required): AWS secret access key.
    • sessionToken: AWS session token (for temporary credentials).
    • service: AWS service name (e.g., lambda, s3).
    • region: AWS region (e.g., us-east-1).
    • cache: Credential cache (defaults to new Map()).
    • retries: Number of retries before giving up (defaults to 10; set to 0 to disable).
    • initRetryMs: Initial retry delay (defaults to 50; doubles each retry).
    import { AwsClient } from 'aws4fetch'
    
    const aws = new AwsClient({
      accessKeyId: 'YOUR_ACCESS_KEY',
      secretAccessKey: 'YOUR_SECRET_KEY',
      sessionToken: 'YOUR_SESSION_TOKEN', // optional
      service: 'lambda',                 // optional
      region: 'us-east-1',               // optional
      cache: new Map(),                 // optional
      retries: 10,                       // optional
      initRetryMs: 50                   // optional
    })
  9. Sign a request with AwsClient.sign()

    master

    The sign method allows you to manually generate a signed Request object without immediately executing the fetch. This is useful if you need to inspect the signed request or use it with a different fetching mechanism.

    Parameters

    • input: A Request object or an object with a toString() method (like a URL string).
    • init (optional): An AwsRequestInit object which extends RequestInit with an aws configuration block.

    AwsRequestInit Options

    The init.aws object allows overriding client settings for a specific request:

    • accessKeyId, secretAccessKey, sessionToken, service, region, cache
    • datetime: Specific ISO string for the request timestamp.
    • signQuery: Boolean. If true, signature is placed in query parameters instead of headers.
    • appendSessionToken: Boolean. If true, appends the session token to query parameters.
    • allHeaders: Boolean. If true, includes all headers in the signature (not just unsignable ones).
    • singleEncode: Boolean. Controls URL encoding behavior.
    // Signing a URL string directly
    const signedRequest = await aws.sign('https://example.com/api');
    
    // Signing with specific overrides
    const signedRequest = await aws.sign(new Request('https://example.com'), {
      aws: {
        service: 'sqs',
        signQuery: true
      }
    });
  10. Use AwsV4Signer for low-level request signing

    master

    AwsV4Signer is a low-level class used to perform the actual AWS Signature Version 4 calculations. While AwsClient is recommended for most use cases, AwsV4Signer provides direct access to the signing components.

    Key Methods

    • sign(): Returns a Promise resolving to an object containing the method, url, headers, and body required for a signed request.
    • authHeader(): Returns a Promise resolving to the Authorization header string.
    • signature(): Returns a Promise resolving to the calculated signature string.

    Constructor Options

    Identical to AwsClient options, plus:

    • method: HTTP method (defaults to POST if a body is present, otherwise GET).
    • url: The target URL.
    • headers: HeadersInit object.
    • body: BodyInit object.
    • datetime: Specific timestamp.
    • signQuery: Whether to sign via query parameters.
    • appendSessionToken: Whether to append session token to query params.
    • allHeaders: Whether to sign all headers.
    • singleEncode: URL encoding preference.
    import { AwsV4Signer } from 'aws4fetch';
    
    const signer = new AwsV4Signer({
      url: 'https://s3.amazonaws.com/bucket/key',
      method: 'GET',
      accessKeyId: 'KEY',
      secretAccessKey: 'SECRET',
      region: 'us-east-1',
      service: 's3'
    });
    
    const signedData = await signer.sign();
    // signedData contains { method, url, headers, body }