Zac Fukuda
070

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.