DEV Community

kuboyumi
kuboyumi

Posted on

Solving the 403 Signature Mismatch: CloudFront + API Gateway (SigV4)

Introduction

When building secure AWS architectures, a common pattern is routing traffic through CloudFront to an API Gateway with IAM Authentication (SigV4) enabled.

However, many developers encounter a frustrating issue: requests that succeed when sent directly to API Gateway fail with a 403 Forbidden error when sent via CloudFront.

The Error Message:
403 Forbidden: The request signature we calculated does not match the signature you provided.

This "Signature Mismatch" is a classic pitfall caused by the way AWS Signature Version 4 (SigV4) interacts with CloudFront's default behavior. This article breaks down why this happens and provides two battle-tested solutions.


The Cause: Request Alteration in Transit

The SigV4 signing process generates a hash based on specific request components (Host, Path, Query Strings, Headers, and Payload). Think of it as a tamper-evident seal: if the request is modified even slightly after being signed, the seal becomes invalid.

When a 403 error occurs via CloudFront, it is highly probable that the request information used by the client for signing has been altered before reaching API Gateway.

The Primary Culprit: Host Header Rewrite

  1. Client-side Signing: The client signs the request using the CloudFront domain (e.g., xxx.cloudfront.net) as the Host
  2. CloudFront Modification: By default, CloudFront overwrites the Host header with the API Gateway origin domain (e.g., yyy.execute-api...) before forwarding
  3. API Gateway Validation: API Gateway recalculates the signature using the Host it received (the APIGW domain). Since this doesn't match the original Host used by the client, the validation fails

Official Reference:
AWS Signature Version 4 Reference

Other Potential Factors

While the Host header is the most common cause, the following can also trigger mismatches:

  • Query Parameter Stripping: If CloudFront is configured to not forward specific query strings
  • Path Normalization: Differences in how CloudFront and API Gateway handle trailing slashes or redundant path segments

Solution 1 (Recommended): Unify the Host with a Custom Domain

The most robust architectural solution is using a shared Custom Domain (e.g., api.example.com) for both CloudFront and API Gateway. This ensures the Host header remains consistent from the client all the way to the origin.

Implementation Checklist

1. API Gateway Configuration

  • Endpoint Type: Select "Regional"
    • Note: Avoid "Edge-optimized" here. Since you are already using your own CloudFront, Edge-optimized creates an unnecessary "Double-CloudFront" setup, increasing latency and debugging complexity
  • ACM Certificate: Create this in the same region as your API Gateway
  • API Mapping: Map your custom domain to the target API stage

2. CloudFront Configuration

  • Alternate Domain Name (CNAME): Add api.example.com
  • Origin Request Policy: Set the policy to forward the Host header (Allowlist)
  • ACM Certificate: You must use a certificate created in the US East (N. Virginia - us-east-1) region for CloudFront

3. DNS (Route 53) Setup

  • Create an A record for api.example.com as an Alias to the CloudFront distribution

Solution 2 (Workaround): Client-side Signature Override

If you cannot change the infrastructure, you can resolve the issue in code. The client sends the request to CloudFront but calculates the signature using the API Gateway origin host.

Python (boto3) Implementation

import requests
from aws_requests_auth.aws_auth import AWSRequestsAuth

# The actual request destination (CloudFront)
url = "[https://xxx.cloudfront.net/v1/resource](https://xxx.cloudfront.net/v1/resource)"

# The host used for signature calculation (API Gateway Origin)
# This separates the request destination from the signing target
api_gateway_host = "yyy.execute-api.ap-northeast-1.amazonaws.com"

auth = AWSRequestsAuth(
    aws_access_key='YOUR_ACCESS_KEY',
    aws_secret_access_key='YOUR_SECRET_KEY',
    aws_host=api_gateway_host, # ★ Fix the signing host to APIGW
    aws_region='ap-northeast-1',
    aws_service='execute-api'
)

# Request is sent to CloudFront, but signed for API Gateway
response = requests.get(url, auth=auth)
print(f"Status: {response.status_code}")
Enter fullscreen mode Exit fullscreen mode

💡 Pro Tip: Testing with Postman

When implementing Solution 2, Postman’s built-in "AWS Signature" authorization cannot be used. This is because Postman automatically extracts the signing host from the request URL and does not allow for the "host mismatch" required in this workaround.

To overcome this limitation, I have published a Postman Pre-request Script that allows you to override the Host header specifically for signature calculation. You can use it to verify your setup before writing any code.

kuboyumi/postman-aws-sigv4-script

Top comments (0)