<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kuboyumi</title>
    <description>The latest articles on DEV Community by kuboyumi (@kuboyumi).</description>
    <link>https://dev.to/kuboyumi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3740612%2F2ac8407a-8aab-4e22-9be6-7eb09bdea8f2.png</url>
      <title>DEV Community: kuboyumi</title>
      <link>https://dev.to/kuboyumi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kuboyumi"/>
    <language>en</language>
    <item>
      <title>Solving the 403 Signature Mismatch: CloudFront + API Gateway (SigV4)</title>
      <dc:creator>kuboyumi</dc:creator>
      <pubDate>Thu, 29 Jan 2026 21:45:28 +0000</pubDate>
      <link>https://dev.to/kuboyumi/solving-the-403-signature-mismatch-cloudfront-api-gateway-sigv4-2239</link>
      <guid>https://dev.to/kuboyumi/solving-the-403-signature-mismatch-cloudfront-api-gateway-sigv4-2239</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building secure AWS architectures, a common pattern is routing traffic through &lt;strong&gt;CloudFront to an API Gateway with IAM Authentication (SigV4) enabled&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;However, many developers encounter a frustrating issue: requests that succeed when sent directly to API Gateway fail with a &lt;strong&gt;403 Forbidden&lt;/strong&gt; error when sent via CloudFront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Error Message:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;403 Forbidden: The request signature we calculated does not match the signature you provided.&lt;/code&gt;&lt;/p&gt;

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




&lt;h2&gt;
  
  
  The Cause: Request Alteration in Transit
&lt;/h2&gt;

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

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

&lt;h3&gt;
  
  
  The Primary Culprit: Host Header Rewrite
&lt;/h3&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Official Reference:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html" rel="noopener noreferrer"&gt;AWS Signature Version 4 Reference&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Other Potential Factors
&lt;/h3&gt;

&lt;p&gt;While the Host header is the most common cause, the following can also trigger mismatches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query Parameter Stripping&lt;/strong&gt;: If CloudFront is configured to not forward specific query strings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path Normalization&lt;/strong&gt;: Differences in how CloudFront and API Gateway handle trailing slashes or redundant path segments&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Solution 1 (Recommended): Unify the Host with a Custom Domain
&lt;/h2&gt;

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

&lt;h3&gt;
  
  
  Implementation Checklist
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. API Gateway Configuration
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint Type&lt;/strong&gt;: Select &lt;strong&gt;"Regional"&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;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&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;ACM Certificate&lt;/strong&gt;: Create this in the same region as your API Gateway&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;API Mapping&lt;/strong&gt;: Map your custom domain to the target API stage&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. CloudFront Configuration
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  3. DNS (Route 53) Setup
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Create an A record for &lt;code&gt;api.example.com&lt;/code&gt; as an &lt;strong&gt;Alias to the CloudFront distribution&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Solution 2 (Workaround): Client-side Signature Override
&lt;/h2&gt;

&lt;p&gt;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 &lt;strong&gt;API Gateway origin host&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python (boto3) Implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_requests_auth.aws_auth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AWSRequestsAuth&lt;/span&gt;

&lt;span class="c1"&gt;# The actual request destination (CloudFront)
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[https://xxx.cloudfront.net/v1/resource](https://xxx.cloudfront.net/v1/resource)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# The host used for signature calculation (API Gateway Origin)
# This separates the request destination from the signing target
&lt;/span&gt;&lt;span class="n"&gt;api_gateway_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yyy.execute-api.ap-northeast-1.amazonaws.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AWSRequestsAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;aws_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_ACCESS_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_gateway_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# ★ Fix the signing host to APIGW
&lt;/span&gt;    &lt;span class="n"&gt;aws_region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ap-northeast-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;execute-api&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Request is sent to CloudFront, but signed for API Gateway
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  💡 Pro Tip: Testing with Postman
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kuboyumi/postman-aws-sigv4-script" rel="noopener noreferrer"&gt;kuboyumi/postman-aws-sigv4-script&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>apigateway</category>
      <category>serverless</category>
      <category>cloudfront</category>
    </item>
  </channel>
</rss>
