DEV Community

Max
Max

Posted on

AWS Lambda with CloudFront: CORS, authorization and caching examples

This post is a collection of examples with different configuration options for invoking AWS Lambda via CloudFront.
It demonstrates how configuration changes affect the lambda function invocations and request/response headers.

Example 1: CORS not required, no authorization headers, no caching

This simple GET request does not require the CORS protocol or authorization:

const response = await fetch("https://d9tskged4za87.cloudfront.net/sync.html");
Enter fullscreen mode Exit fullscreen mode

Function URL configuration:

  • Auth type: AWS_IAM
  • CORS disabled (irrelevant as no CORS protocol is involved)

CloudFront configuration:

  • origin access with signed requests
  • CachingDisabled policy
  • OPTIONS caching setting is irrelevant since caching is disabled via CachingDisabled policy
  • Managed-AllViewerExceptHostHeader request policy
  • No response policy

How it works:

  1. CloudFront replaces the value of Host and passes all other headers onto the lambda
  2. the lambda function handler is invoked for all HTTP request types
  3. all headers generated by the lambda function are forwarded back to the client

Request headers

Browser to CloudFront:

GET /sync.html HTTP/2
Host: d9tskged4za87.cloudfront.net
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br, zstd
Origin: https://localhost:8080
DNT: 1
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Priority: u=0
Pragma: no-cache
Cache-Control: no-cache
Enter fullscreen mode Exit fullscreen mode

Headers in the payload passed to the lambda function:

{
  "accept": "*/*",
  "accept-encoding": "gzip, deflate, br, zstd",
  "accept-language": "en-US,en;q=0.5",
  "cache-control": "no-cache",
  "cloudfront-forwarded-proto": "https",
  "cloudfront-is-android-viewer": "false",
  "cloudfront-is-desktop-viewer": "true",
  "cloudfront-is-ios-viewer": "false",
  "cloudfront-is-mobile-viewer": "false",
  "cloudfront-is-smarttv-viewer": "false",
  "cloudfront-is-tablet-viewer": "false",
  "cloudfront-viewer-address": "2406:2d40:728d:fa10::c4a:42838",
  "cloudfront-viewer-asn": "14593",
  "cloudfront-viewer-city": "Auckland",
  "cloudfront-viewer-country": "NZ",
  "cloudfront-viewer-country-name": "New Zealand",
  "cloudfront-viewer-country-region": "AUK",
  "cloudfront-viewer-country-region-name": "Auckland",
  "cloudfront-viewer-http-version": "2.0",
  "cloudfront-viewer-latitude": "-36.85060",
  "cloudfront-viewer-longitude": "174.76790",
  "cloudfront-viewer-postal-code": "1010",
  "cloudfront-viewer-time-zone": "Pacific/Auckland",
  "cloudfront-viewer-tls": "TLSv1.3:TLS_AES_128_GCM_SHA256:sessionResumed",
  "dnt": "1",
  "host": "mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws",
  "origin": "https://localhost:8080",
  "pragma": "no-cache",
  "priority": "u=0",
  "sec-fetch-dest": "empty",
  "sec-fetch-mode": "cors",
  "sec-fetch-site": "cross-site",
  "user-agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0",
  "via": "2.0 03e8784cc6fbcd65ff743e9f537e8e88.cloudfront.net (CloudFront)",
  "x-amz-cf-id": "Wtw2gvr0...o3nk-DQ==",
  "x-amz-content-sha256": "e3b0c44...852b855",
  "x-amz-date": "20240812T024912Z",
  "x-amz-security-token": "IQoJb3...yM6wdjMEg==",
  "x-amz-source-account": "512295225992",
  "x-amz-source-arn": "arn:aws:cloudfront::512295225992:distribution/E3FGXRC3VXQ2IF",
  "x-amzn-tls-cipher-suite": "ECDHE-RSA-AES128-GCM-SHA256",
  "x-amzn-tls-version": "TLSv1.2",
  "x-amzn-trace-id": "Root=1-66b97828-7217f26e68fb64c806f4daf5",
  "x-forwarded-for": "2406:2d40:728d:fa10::c4a",
  "x-forwarded-port": "443",
  "x-forwarded-proto": "https"
}
Enter fullscreen mode Exit fullscreen mode

Request headers of interest:

  • browser to CloudFront Host: d9tskged4za87.cloudfront.net was replaced by CloudFront
  • CloudFront to Lambda: host: mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws
  • origin: https://localhost:8080 is the same for both

Response headers

Lambda's response:

{
  "statusCode": 200,
  "headers": {
    "x-foo-header": "bar",
    "content-type": "text/html; charset=utf-8"
  },
  "body": "Hello from client-sync",
  "isBase64Encoded": false,
  "cookies": []
}
Enter fullscreen mode Exit fullscreen mode

CloudFront to the browser:

HTTP/2 200 
content-type: text/html; charset=utf-8
content-length: 22
date: Mon, 12 Aug 2024 02:49:14 GMT
x-amzn-requestid: 28b96338-a3a6-4a6e-aad2-8b7d25a4fe13
x-foo-header: bar
vary: Origin
x-amzn-trace-id: root=1-66b97828-7217f26e68fb64c806f4daf5;parent=717724cd0dc027c2;sampled=0;lineage=a964c7ca:0
x-cache: Miss from cloudfront
via: 1.1 03e8784cc6fbcd65ff743e9f537e8e88.cloudfront.net (CloudFront)
x-amz-cf-pop: LAX3-C3
x-amz-cf-id: Wtw2gvr045gDD6eqIC6s_rDEnugktHpg0YUjmrp2jRuN6PNo3nk-DQ==
X-Firefox-Spdy: h2
Enter fullscreen mode Exit fullscreen mode

Response headers of interest:

  • x-foo-header: bar custom header was passed from the lambda function to the browser
  • x-cache: Miss from cloudfront CloudFront header is always Miss ... because the caching is disabled

Example 2: CORS with custom authorization header and no caching

This request requires the CORS protocol, but the authorization is done through a custom header:

const response = await fetch(
  "https://d9tskged4za87.cloudfront.net/sync.html",
  {
    headers: {
      "X-Books-Authorization": "dummy-book-auth4",
    },
  },
);
Enter fullscreen mode Exit fullscreen mode

In Function URL:

  • Auth type: AWS_IAM
  • CORS enabled

In CloudFront (unchanged):

  • origin access with signed requests
  • CachingDisabled policy
  • OPTIONS caching is disabled
  • Managed-AllViewerExceptHostHeader request policy
  • No response policy

Flow changes:

  1. The browser sends the HTTP OPTIONS request first to obtain the CORS headers: OPTIONS then GET request sequence
  2. CORS headers are added by AWS Lambda as part of the lambda handler invocation
  3. The browser sends a GET request after checking the CORS headers

OPTIONS request/response

CORS-related request headers for HTTP OPTIONS request:

OPTIONS /sync.html HTTP/2
Access-Control-Request-Method: GET
Access-Control-Request-Headers: x-books-authorization
Origin: https://localhost:8080
Enter fullscreen mode Exit fullscreen mode

CloudFront OPTIONS response has four CORS headers generated by AWS Lambda without invoking the lambda function handler:

access-control-allow-origin: https://localhost:8080
access-control-allow-headers: authorization,content-type,x-books-authorization
access-control-allow-methods: GET,POST
access-control-allow-credentials: true
Enter fullscreen mode Exit fullscreen mode

GET request/response

The OPTIONS request is followed by a GET request with a custom X-Books-Authorization header:

GET /sync.html HTTP/2
X-Books-Authorization: dummy-book-auth4
Origin: https://localhost:8080
...
Enter fullscreen mode Exit fullscreen mode

The lambda function handler receives the X-Books-Authorization header inside the GET request payload:

{
  "x-books-authorization": "dummy-book-auth4",
  ...
}
Enter fullscreen mode Exit fullscreen mode

The lambda's response is identical to the previous GET example with no CORS:

{
  "statusCode": 200,
  "headers": {
    "x-foo-header": "bar",
    "content-type": "text/html; charset=utf-8"
  },
  "body": "Hello from client-sync",
  "isBase64Encoded": false,
  "cookies": []
}
Enter fullscreen mode Exit fullscreen mode

CloudFront GET response has two CORS headers added to the Lambda's response by AWS Lambda (not the handler):

access-control-allow-origin: https://localhost:8080
access-control-allow-credentials: true
Enter fullscreen mode Exit fullscreen mode

Example 3: CORS with Authorization header and caching

This request uses the Authorization header and requires the CORS protocol:

const response = await fetch(
  "https://d9tskged4za87.cloudfront.net/sync.html",
  {
    headers: {
      "Authorization": "dummy-auth",
    },
  },
);
Enter fullscreen mode Exit fullscreen mode

In Function URL:

  • Auth type: NONE
  • CORS enabled

In CloudFront:

  • origin access with unsigned requests
  • custom caching policy that includes the Authorization header
  • OPTIONS caching is enabled
  • Managed-AllViewerExceptHostHeader request policy
  • No response policy

Flow changes:

  • the Lambda function is public (Auth type: NONE)
  • CloudFront no longer uses the Authorization header (no request signing)
  • repeat requests with the same value for the Authorization header are served from the CloudFront cache
  • repeat OPTIONS requests are served from the CloudFront cache

All the request/response headers are nearly identical to the previous example, except for the common Authorization header replacing the custom X-Books-Authorization header.
The Authorization header is passed to the lambda function handler as part of the payload.

OPTIONS request/response:

  • identical to the previous example

GET request headers

From browser to CloudFront:

GET /sync.html HTTP/2
Host: d9tskged4za87.cloudfront.net
Authorization: dummy-auth
Origin: https://localhost:8080
...
Enter fullscreen mode Exit fullscreen mode

The Authorization header was added to the lambda handler payload:

{
  "authorization": "dummy-auth",
  ...
}
Enter fullscreen mode Exit fullscreen mode

Caching

Initial OPTIONS/GET requests:

  • both OPTIONS and GET return x-cache: Miss from cloudfront header
  • AWS Lambda (not the handler) responds to OPTIONS request
  • the lambda handler is invoked for the GET request

Repeat OPTIONS/GET requests with the same Authorization value:

  • both return x-cache: Hit from cloudfront header
  • no lambda invocations

Repeat OPTIONS/GET requests with different Authorization values:

  • OPTIONS returns x-cache: Hit from cloudfront header
  • GET returns x-cache: Miss from cloudfront header
  • the lambda handler is invoked for the GET request

Top comments (0)