> ## Documentation Index
> Fetch the complete documentation index at: https://connect-docs.supertab.co/llms.txt
> Use this file to discover all available pages before exploring further.

# CloudFront

> Publish RSL license and deploy CAP enforcement on AWS CloudFront.

Supertab Connect integrates with AWS CloudFront for two purposes: serving your RSL license at `/license.xml` via your domain, and enforcing the Crawler Authentication Protocol (CAP) using Lambda\@Edge and CloudFront Functions.

***

## Publishing RSL License

Your RSL license needs to be accessible at `https://yourdomain.com/license.xml`. CloudFront proxies this path to the Supertab Connect origin using a CloudFront Function for URI rewriting, a new origin, and a dedicated cache behavior.

### CloudFront Function

Create a function with runtime `cloudfront-js-2.0`. This runs on viewer request and rewrites the URI before CloudFront selects the origin.

```javascript theme={null}
function handler(event) {
  var request = event.request;
  var merchantURN = "YOUR_WEBSITE_URN";
  request.uri = "/merchants/systems/" + merchantURN + request.uri;
  return request;
}
```

Publish the function after saving.

### Origin

Add an origin to your distribution:

```
Origin domain:  api-connect.supertab.co
Protocol:       HTTPS only
Name:           supertab-connect-origin
```

### Cache Behavior

Create a cache behavior for `/license.xml`:

| Setting                 | Value                              |
| ----------------------- | ---------------------------------- |
| Path pattern            | `/license.xml`                     |
| Origin                  | `supertab-connect-origin`          |
| Viewer protocol policy  | Redirect HTTP to HTTPS             |
| Cache policy            | CachingOptimized                   |
| Origin request policy   | `AllViewerExceptHostHeader`        |
| Viewer request function | Your published CloudFront Function |

<Note>
  Use `AllViewerExceptHostHeader`, not `AllViewer`. The SDK needs viewer headers like `User-Agent` for bot detection, but forwarding the `Host` header causes origin routing failures.
</Note>

The `/license.xml` behavior must sit above the default `*` behavior in the behaviors list. Deployment takes 10–15 minutes after saving.

<Accordion title="Terraform Alternative">
  If you manage your distribution with Terraform, use this configuration instead of the manual steps above.

  ```hcl theme={null}
  resource "aws_cloudfront_function" "supertab_rewrite_license_path" {
    name    = "supertab-rewrite-license-path"
    runtime = "cloudfront-js-2.0"
    publish = true
    comment = "Rewrites /license.xml to the Supertab Connect URN path"

    code = <<-EOT
  function handler(event) {
    var request = event.request;
    var merchantURN = "YOUR_WEBSITE_URN";
    request.uri = "/merchants/systems/" + merchantURN + request.uri;
    return request;
  }
  EOT
  }

  resource "aws_cloudfront_distribution" "your_distribution" {
    # ... your existing config ...

    origin {
      domain_name = "api-connect.supertab.co"
      origin_id   = "supertab-connect-origin"

      custom_origin_config {
        http_port              = 80
        https_port             = 443
        origin_protocol_policy = "https-only"
        origin_ssl_protocols   = ["TLSv1.2"]
      }
    }

    ordered_cache_behavior {
      path_pattern     = "/license.xml"
      allowed_methods  = ["GET", "HEAD"]
      cached_methods   = ["GET", "HEAD"]
      target_origin_id = "supertab-connect-origin"

      forwarded_values {
        query_string = false
        headers      = ["Origin"]
        cookies { forward = "none" }
      }

      function_association {
        event_type   = "viewer-request"
        function_arn = aws_cloudfront_function.supertab_rewrite_license_path.arn
      }

      viewer_protocol_policy = "redirect-to-https"
      min_ttl                = 0
      default_ttl            = 86400
      max_ttl                = 31536000
      compress               = true
    }

    depends_on = [aws_cloudfront_function.supertab_rewrite_license_path]
  }
  ```
</Accordion>

***

## CAP Enforcement

CloudFront CAP deployment combines two edge features:

* **CloudFront Function** (viewer request): Identifies `Authorization: License` headers on every request, including cache hits, without adding latency for regular traffic.
* **Lambda\@Edge** (origin request): Runs the Supertab Connect SDK to verify the token and record usage. Fires only on cache misses.

<Note>
  CloudFront Functions fire on every request including cache hits. Lambda\@Edge origin-request fires only on cache misses. This distinction matters for billing and completeness of usage recording.
</Note>

Lambda\@Edge functions must be deployed in **us-east-1**.

### Prerequisites

You need Node.js 22+, npm, and the AWS CLI configured (`aws configure` or `aws login`).

If you have `PowerUserAccess`, that covers all IAM requirements. Otherwise, the deploying user needs a scoped policy.

<Accordion title="Required IAM policy (replace YOUR_DISTRIBUTION_ID)">
  ```json theme={null}
  {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Sid": "CLILogin",
        "Effect": "Allow",
        "Action": [
          "signin:AuthorizeOAuth2Access",
          "signin:CreateOAuth2Token"
        ],
        "Resource": "*"
      },
      {
        "Sid": "IAMRole",
        "Effect": "Allow",
        "Action": [
          "iam:CreateRole",
          "iam:GetRole",
          "iam:AttachRolePolicy",
          "iam:PassRole"
        ],
        "Resource": "arn:aws:iam::*:role/supertab-edge-verify*"
      },
      {
        "Sid": "AllowServiceLinkedRoleForLambdaEdge",
        "Effect": "Allow",
        "Action": "iam:CreateServiceLinkedRole",
        "Resource": "arn:aws:iam::*:role/aws-service-role/replicator.lambda.amazonaws.com/*",
        "Condition": {
          "StringEquals": {
            "iam:AWSServiceName": "replicator.lambda.amazonaws.com"
          }
        }
      },
      {
        "Sid": "LambdaEdge",
        "Effect": "Allow",
        "Action": [
          "lambda:CreateFunction",
          "lambda:UpdateFunctionCode",
          "lambda:UpdateFunctionConfiguration",
          "lambda:GetFunction",
          "lambda:GetFunctionConfiguration",
          "lambda:PublishVersion",
          "lambda:AddPermission",
          "lambda:EnableReplication*"
        ],
        "Resource": "arn:aws:lambda:us-east-1:*:function:supertab-verify*"
      },
      {
        "Sid": "CloudFrontFunctionCreate",
        "Effect": "Allow",
        "Action": ["cloudfront:CreateFunction", "cloudfront:ListFunctions"],
        "Resource": "*"
      },
      {
        "Sid": "CloudFrontFunctionManage",
        "Effect": "Allow",
        "Action": ["cloudfront:GetFunction", "cloudfront:DescribeFunction", "cloudfront:UpdateFunction", "cloudfront:PublishFunction"],
        "Resource": "arn:aws:cloudfront::*:function/supertab-*"
      },
      {
        "Sid": "CloudFrontCachePolicyManage",
        "Effect": "Allow",
        "Action": ["cloudfront:ListCachePolicies", "cloudfront:CreateCachePolicy"],
        "Resource": "*"
      },
      {
        "Sid": "CloudFrontDistributionManage",
        "Effect": "Allow",
        "Action": [
          "cloudfront:GetDistribution",
          "cloudfront:GetDistributionConfig",
          "cloudfront:UpdateDistribution"
        ],
        "Resource": "arn:aws:cloudfront::*:distribution/YOUR_DISTRIBUTION_ID"
      }
    ]
  }
  ```
</Accordion>

### Step 1: Build the Lambda Package

```bash theme={null}
mkdir supertab-verify && cd supertab-verify
npm init -y
npm install @getsupertab/supertab-connect-sdk
npm install -D esbuild typescript @types/aws-lambda
```

Create `index.ts`:

```typescript theme={null}
import { SupertabConnect } from "@getsupertab/supertab-connect-sdk";
import type { CloudFrontRequestEvent, CloudFrontRequestResult } from "aws-lambda";

export async function handler(
  event: CloudFrontRequestEvent
): Promise<CloudFrontRequestResult> {
  return SupertabConnect.cloudfrontHandleRequests(event, {
    apiKey: "YOUR_MERCHANT_API_KEY", // from your Supertab Connect dashboard
  });
}
```

Add build scripts to `package.json`:

```json theme={null}
{
  "scripts": {
    "build": "esbuild index.ts --bundle --platform=node --target=node20 --outfile=dist/index.js --format=cjs",
    "package": "cd dist && zip -r function.zip index.js",
    "bundle": "npm run build && npm run package"
  }
}
```

Build:

```bash theme={null}
npm run bundle
```

This produces `dist/function.zip`.

### Step 2: Deploy to AWS

**IAM execution role** — Create a role that both Lambda and CloudFront's edge service can assume. Attach `AWSLambdaBasicExecutionRole` for CloudWatch logging.

Trust policy:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": ["lambda.amazonaws.com", "edgelambda.amazonaws.com"]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

**Lambda function** — Create the function in `us-east-1` (required for Lambda\@Edge — CloudFront replicates it globally from there). Upload `dist/function.zip` with `nodejs20.x` runtime and the execution role from above.

**Publish a version** — Lambda\@Edge requires a published, numbered version ARN. Do not attach `$LATEST`. After publishing, grant CloudFront permission to invoke the function at edge locations (`lambda:GetFunction` and `lambda:InvokeFunction` for the `edgelambda.amazonaws.com` principal).

Save the published version ARN — you need it in Step 4.

<Note>
  If you update your handler code later, upload the new zip, publish a new version, and update the behavior to point to the new version ARN.
</Note>

### Step 3: Create the Filtering Function

Create a CloudFront Function (`cloudfront-js-2.0` runtime) that runs on viewer request. It checks whether the `Authorization` header contains a license token. If it does, it adds `x-license-auth` to the request headers — this header is used in the cache key to separate licensed and unlicensed cache entries, and its presence triggers the Lambda\@Edge function on cache misses.

```javascript theme={null}
function isLicenseRequest(request) {
  var authHeader = (request.headers || {}).authorization;
  var authValue = authHeader && authHeader.value ? authHeader.value : "";
  return authValue.length > 7 && authValue.slice(0, 8).toLowerCase() === "license ";
}

function handler(event) {
  var request = event.request;
  var headers = request.headers || {};

  if (isLicenseRequest(request)) {
    request.headers["x-license-auth"] = { value: event.context.requestId };
    request.headers["x-original-request-url"] = {
      value: (headers.host && headers.host.value ? headers.host.value : "") + request.uri,
    };
  }

  return request;
}
```

Publish the function after saving.

### Step 4: Configure the Cache Behavior

Edit the behavior for the path you want to protect (the default `*` behavior for all requests, or a specific path like `/articles/*`).

**Cache policy** — Create a custom cache policy with `x-license-auth` included in the cache key headers. This ensures that requests with and without a license token produce separate cache entries — without it, a cached response from a licensed request could be served to unlicensed requests. Keep defaults for TTL, query strings, cookies, and compression.

**Origin request policy** — Set to `AllViewerExceptHostHeader`. This forwards viewer headers (including `User-Agent` for bot detection) to the origin while letting CloudFront set the correct origin hostname.

<Note>
  Do not use `AllViewer` — it forwards the original `Host` header, which causes routing failures on S3 and API Gateway origins.
</Note>

**Function associations:**

| Event          | Type                 | Value                                         |
| -------------- | -------------------- | --------------------------------------------- |
| Viewer request | CloudFront Functions | Your published filtering function from Step 3 |
| Origin request | Lambda\@Edge         | Published version ARN from Step 2             |

Save and wait for the distribution to deploy (10–15 minutes).

***

## Related Docs

<CardGroup cols={2}>
  <Card title="Deploy in Your CDN" icon="shield" href="/guides/deploy-cdn">
    CDN-agnostic guide covering RSL serving, CAP enforcement, and robots.txt.
  </Card>

  <Card title="Other CDNs" icon="server" href="/reference/others">
    Generic CDN patterns for platforms not listed above.
  </Card>
</CardGroup>
