Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/semantic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ scopes:
- deps-dev
- roadmap
- kafka
- signer

# Always validate the PR title
# and ignore the commits to lower the entry bar for contribution
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/run-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
packages/event-handler,
packages/tracer,
packages/batch,
packages/signer,
layers,
]
version: [22, 24]
Expand Down
8 changes: 8 additions & 0 deletions docs/features/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,12 @@ description: Features of Powertools for AWS Lambda

[:octicons-arrow-right-24: Read more](./kafka.md)

- __Signer__

---

Sign HTTP requests to AWS services using the AWS Signature Version 4 (SigV4) signing process, with an optional drop-in signed `fetch`.

[:octicons-arrow-right-24: Read more](./signer.md)

</div>
85 changes: 85 additions & 0 deletions docs/features/signer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
title: Signer
descrition: Utility
---

<!-- markdownlint-disable MD043 --->

This utility provides a way to sign HTTP requests to AWS services using the [AWS Signature Version 4 (SigV4)](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv4_signing.html){target="_blank"} signing process, so you can call IAM-authenticated endpoints such as Amazon API Gateway, AWS Lambda function URLs, or AWS AppSync from within your Lambda functions.

## Key features

* Sign web-standard `Request` objects with AWS Signature Version 4
* Drop-in signed `fetch` for sending signed requests in one step
* Works with any HTTP client by exposing the signed request and headers
* Reads credentials and region from the Lambda runtime by default, with no extra dependencies

## Getting started

```bash
npm install @aws-lambda-powertools/signer
```

The signer takes a web-standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request){target="_blank"} (or anything you can pass to `fetch`, like a URL string) and returns a new, signed `Request` with the SigV4 headers added. It performs no network I/O, so you stay in control of how the request is sent.

```typescript hl_lines="1 3 7"
--8<-- "examples/snippets/signer/gettingStarted.ts"
```

By default, the signer reads the AWS credentials and region from the [environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime){target="_blank"} that the Lambda runtime always provides, so no additional configuration is required when running in Lambda.

!!! note "The `service` is required"
The service name (for example `execute-api`, `lambda`, or `appsync`) cannot be reliably determined from the request URL, since custom domains and Amazon CloudFront hide the underlying service. You must always provide it.

## Sending signed requests

If all you want is to sign and immediately send the request, use `createSignedFetcher`. It consumes a signer instance and returns a function with the same signature as the global `fetch`, signing each request before sending it.

```typescript hl_lines="1 2 5"
--8<-- "examples/snippets/signer/fetcher.ts"
```

Because the returned function is a drop-in `fetch`, you can also pass it to libraries that accept a custom `fetch` implementation.

## Using other HTTP clients

Signing and sending are deliberately kept separate, so you can use the signed request with any HTTP client (for example `axios`, `got`, a generated SDK client, or a request interceptor). Call `sign()` to obtain a signed `Request`, then read its `url`, `method`, and `headers`.

```typescript hl_lines="11-15"
--8<-- "examples/snippets/signer/headers.ts"
```

## Advanced

### Configuring the region

The region defaults to the `AWS_REGION` environment variable that Lambda sets. You can override it, for example to sign requests for a service in a different region.

```typescript hl_lines="5"
--8<-- "examples/snippets/signer/region.ts"
```

1. The `region` option takes precedence over the `AWS_REGION` environment variable.

### Configuring credentials

When running outside of Lambda, the standard AWS credentials environment variables may not be set. In that case, pass your own credentials or a credential provider, such as `fromNodeProviderChain()` from [`@aws-sdk/credential-provider-node`](https://www.npmjs.com/package/@aws-sdk/credential-provider-node){target="_blank"}, which you install yourself.

```typescript hl_lines="6-12"
--8<-- "examples/snippets/signer/credentials.ts"
```

### Handling errors

The signer throws typed errors that all extend `SignerError`:

| Error | When it is thrown |
| --------------------- | ---------------------------------------------------------------------------------------------------------- |
| `SignerConfigError` | The region cannot be determined (at construction), or credentials are missing or cannot be resolved. |
| `RequestSigningError` | Signing the request fails, for example because the request body cannot be read or replayed. |
| `SignerError` | Base class for the errors above. Catch this to handle any signer error. |

You can import them from the `@aws-lambda-powertools/signer/errors` subpath.

!!! note "Request bodies"
To compute the request signature, the request body is buffered and hashed. Strings, buffers, and finite streams are handled transparently. A body that cannot be read or replayed — for example a stream that errors mid-read — cannot be signed and raises a `RequestSigningError`.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Powertools for AWS Lambda (TypeScript) is built as a modular toolkit, so you can
| [Parser](./features/parser.md) | Utility to parse and validate AWS Lambda event payloads using Zod, a TypeScript-first schema declaration and validation library. |
| [Validation](./features/validation.md) | JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation. |
| [Kafka](./features/kafka.md) | Utility to easily handle message deserialization and parsing of Kafka events in AWS Lambda functions. |
| [Signer](./features/signer.md) | Sign HTTP requests to AWS services using the AWS Signature Version 4 (SigV4) signing process, with an optional drop-in signed `fetch`. |

## Examples

Expand Down
1 change: 1 addition & 0 deletions examples/snippets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@aws-lambda-powertools/metrics": "^2.33.1",
"@aws-lambda-powertools/parameters": "^2.33.1",
"@aws-lambda-powertools/parser": "^2.33.1",
"@aws-lambda-powertools/signer": "^2.33.1",
"@aws-lambda-powertools/tracer": "^2.33.1",
"@aws-sdk/client-appconfigdata": "^3.1067.0",
"@aws-sdk/client-dynamodb": "^3.1067.0",
Expand Down
21 changes: 21 additions & 0 deletions examples/snippets/signer/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SigV4Signer } from '@aws-lambda-powertools/signer/sigv4';

// By default, credentials are read from the standard AWS environment variables
// that the Lambda runtime injects. When running outside of Lambda, pass your
// own credentials or a credential provider, for example
// `fromNodeProviderChain()` from `@aws-sdk/credential-provider-node`.
const signer = new SigV4Signer({
service: 'execute-api',
credentials: {
accessKeyId: process.env.MY_ACCESS_KEY_ID ?? '',
secretAccessKey: process.env.MY_SECRET_ACCESS_KEY ?? '',
},
});

export const handler = async () => {
const signed = await signer.sign(
'https://example.execute-api.us-east-1.amazonaws.com/items'
);

await fetch(signed);
};
18 changes: 18 additions & 0 deletions examples/snippets/signer/fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createSignedFetcher } from '@aws-lambda-powertools/signer/fetch';
import { SigV4Signer } from '@aws-lambda-powertools/signer/sigv4';

const signer = new SigV4Signer({ service: 'execute-api' });
const signedFetch = createSignedFetcher(signer);

export const handler = async () => {
// `signedFetch` is a drop-in `fetch` that signs each request before sending it
const response = await signedFetch(
'https://example.execute-api.us-east-1.amazonaws.com/items',
{
method: 'POST',
body: JSON.stringify({ name: 'powertools' }),
}
);

await response.json();
};
13 changes: 13 additions & 0 deletions examples/snippets/signer/gettingStarted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SigV4Signer } from '@aws-lambda-powertools/signer/sigv4';

const signer = new SigV4Signer({ service: 'execute-api' });

export const handler = async () => {
const signed = await signer.sign(
'https://example.execute-api.us-east-1.amazonaws.com/items'
);

// `signed` is a standard `Request` with the SigV4 headers added
const response = await fetch(signed);
await response.json();
};
20 changes: 20 additions & 0 deletions examples/snippets/signer/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SigV4Signer } from '@aws-lambda-powertools/signer/sigv4';

const signer = new SigV4Signer({ service: 'execute-api' });

export const handler = async () => {
const signed = await signer.sign(
'https://example.execute-api.us-east-1.amazonaws.com/items',
{ method: 'POST', body: JSON.stringify({ name: 'powertools' }) }
);

// Extract the signed headers to use them with any HTTP client, e.g. an
// interceptor for axios, got, or a generated SDK client.
const headers: Record<string, string> = {};
for (const [key, value] of signed.headers) {
headers[key] = value;
}

// `signed.url`, `signed.method`, and `headers` can now be passed to the
// client of your choice.
};
16 changes: 16 additions & 0 deletions examples/snippets/signer/region.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SigV4Signer } from '@aws-lambda-powertools/signer/sigv4';

const signer = new SigV4Signer({
service: 'execute-api',
region: 'eu-west-1', // (1)!
});

export const handler = async () => {
const signed = await signer.sign(
'https://example.execute-api.eu-west-1.amazonaws.com/items'
);

await fetch(signed);
};

export { signer };
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ nav:
- features/parser.md
- features/validation.md
- features/kafka.md
- features/signer.md
- features/metadata.md
- Environment variables: environment-variables.md
- Upgrade guide: upgrade.md
Expand Down Expand Up @@ -194,6 +195,7 @@ plugins:
- features/parser.md
- features/validation.md
- features/kafka.md
- features/signer.md
- features/metadata.md
Environment variables:
- environment-variables.md
Expand Down
20 changes: 19 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"examples/app",
"packages/event-handler",
"packages/validation",
"packages/kafka"
"packages/kafka",
"packages/signer"
],
"type": "module",
"scripts": {
Expand Down
Loading
Loading