SigV4 to API Gateway in TypeScript
This article shows you how to make an AWS Signature Version 4(SigV4) onto requests sent to your API Gateway HTTP/REST APIs, resources of which require IAM authorization. The sample code is TypeScript.
You might want to use AWS SDK to manually sign your HTTP requests with SigV4. Unfortunately, there is no such thing in early 2026.
Code
/* SigV4Signer.ts */
import { createHash, createHmac } from 'node:crypto';
export class SigV4Signer {
public async sign(request: Request, options : {
region: string;
service: string;
credentials: {
accessKeyId: string;
secretAccessKey: string;
sessionToken: string;
};
}): Promise {
const { region, service, credentials } = options;
const requestClone = request.clone();
const now = new Date();
const isoDate = now.toISOString();
const amzDate = isoDate.replace(/[:-]|\.\d{3}/g, ''); // e.x. 2025-12-31T23:59:50.000Z -> 20251231T1235959Z
const url = new URL(request.url);
const headers = new Headers(request.headers);
headers.set('Host', url.host);
headers.set('X-Amz-Date', amzDate);
headers.set('X-Amz-Security-Token', credentials.sessionToken);
// 1. Create a canonical request
const canonicalUri = url.pathname;
const canonicalQueryString = url.searchParams.toString();
const canonicalHeaders = Array.from(headers.entries())
.map(([key, value]) => `${key.toLowerCase()}:${value}\n`)
.join('');
const signedHeaders = Array.from(headers.keys())
.map((key) => key.toLowerCase())
.join(';');
const payload = await requestClone.text();
const payloadHash = createHash('sha256').update(payload).digest('hex');
const canonicalRequest = [
request.method,
canonicalUri,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
payloadHash,
].join('\n');
// 2. Create a hash of the canonical request
const canonicalRequestHash = createHash('sha256').update(canonicalRequest).digest('hex');
// 3. Create a string to sign
const algorithm = 'AWS4-HMAC-SHA256';
const requestDateTime = amzDate;
const yyyymmdd = requestDateTime.slice(0, 8); // e.x. 20251231T1235959Z -> 20251231
const terminationString = 'aws4_request';
const credentialScope = [
yyyymmdd,
region,
service,
terminationString,
].join('/');
const stringToSign = [
algorithm,
requestDateTime,
credentialScope,
canonicalRequestHash,
].join('\n');
// 4. Derive a signing key
const dateKey = createHmac('sha256', 'AWS4' + credentials.secretAccessKey).update(yyyymmdd).digest();
const dateRegionKey = createHmac('sha256', dateKey).update(region).digest();
const dateRegionServiceKey = createHmac('sha256', dateRegionKey).update(service).digest();
const signingKey = createHmac('sha256', dateRegionServiceKey).update(terminationString).digest();
// 5. Calculate the signature
const signature = createHmac('sha256', signingKey).update(stringToSign).digest('hex');
// 6. Add the signature to the request
const credential = [
credentials.accessKeyId,
credentialScope,
].join('/');
const authorization = algorithm + ' ' + [
'Credential=' + credential,
'SignedHeaders=' + signedHeaders,
'Signature=' + signature,
].join(', ');
headers.set('Authorization', authorization);
return new Request(request, { headers });
}
}
Although code is not provided, the official document Create a signed AWS API request gives you the procedures.
Example
const signer = new SigV4Signer();
const request = new Request('https://YOUR_API_ID.execute-api.REGION.amazonaws.com/STAGE/RESOURE_PATH', {
method: 'POST', // GET, POST, PUT, DELETE
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ foo: 'foo' }),
});
const region = 'us-east-1';
const service = 'execute-api';
const credentials = {
accessKeyId: 'foo',
secretAccessKey: 'bar',
sessionToken: 'baz',
}
const options = { region, service, credentials }
const signedRequest = await signer.sign(request, options);
const response = await fetch(signedRequest);
Comments
No code is perfect. Here are things I’d like to mention.
Only Text
The SigV4Signer class can sign only text-body requests, i.e. JSON strings most cases. There is a silent agreement among developers: you should use JSON string for REST APIs unless it’s not a file upload. I don’t know why we ended up this. This fact, nevertheless, makes the code simpler.
Class over Function
Forty years ago, software engineers struggled with object-oriented programming v.s. functional(traditional) programming. They still do today. You cannot call class driven program object-oriented, neither can you call function driven program functional. But at least it indicates engineers’ preference. Current JavaScript/TypeScript developers prefer function. Functions make codes one-indent less. They allow us to omit this. AWS SDKs, though, use class. I followed AWS about the issue.
How to Get Credential
You can visit Temporary security credentials in IAM.