# How to use Edge Content Protection

Source: https://dev.arcxp.com/subscriptions/content-protection-metering/learn/how-to-use-edge-content-protection/

Configure Edge Content Protection to deliver differentiated reader experiences at the edge using JWT entitlement claims, a rules document, and PageBuilder template variants.

[Edge Content Protection](https://dev.arcxp.com/subscriptions/content-protection-metering/learn/what-is-edge-content-protection/) (ECP) runs at Arc’s edge nodes and appends a `variant` query parameter to incoming request URLs before they are served by the CDN or PageBuilder. The variant is determined by validating a JWT cookie you provide and evaluating a priority-ordered rules document.

> **Note:**
> 
> Edge Content Protection requires provisioning before you can configure it. To enable it for your organization, submit a ticket through the [Arc XP Customer Support (ACS) portal](https://arcpublishing.atlassian.net/servicedesk/customer/portal/2).

## Request Flow

1. **Request arrives at the edge**  
ECP intercepts the incoming request.
2. **Path check**  
If include-paths or exclude-paths patterns are configured, ECP checks the request path against them before any cookie or JWT processing:

  - If an include-paths pattern is configured, requests that do not match are forwarded without a `variant` parameter and no further processing occurs.
  - If an exclude-paths pattern matches, the request is forwarded without a `variant` parameter and no further processing occurs.  
Patterns are `;`\-separated and support `*` and `?` wildcards. See [Configuration Parameters](#configuration-parameters) for details.
3. **Cookie lookup**  
ECP looks for the cookie configured in the Delivery admin UI. If the cookie is absent, the request is forwarded to the CDN/PageBuilder without a `variant` parameter and the default PageBuilder template is used.
4. **JWT validation**  
The cookie value is parsed as a signed JWT. The following checks are performed:

| Check              | Rule                                                               |
| ------------------ | ------------------------------------------------------------------ |
| Algorithm          | Must be HS256 or HS512                                             |
| exp claim          | Must be present and in the future                                  |
| Signature          | Verified using the HMAC secret configured in the Delivery admin UI |
| entitlements claim | Must be present; absence renders the JWT invalid                   |  
If validation fails, see [Invalid JWT Handling](#invalid-jwt-handling).
5. **Rules evaluation**  
The rules engine evaluates each rule in the uploaded rules document in priority order. A rule matches when **both** of the following are true:

  - The `uri-pattern` regular expression matches the incoming request URL.
  - The `entitlements-pattern` regular expression matches the `entitlements` claim value from the JWT.  
Processing stops at the first matching rule.
6. **Variant construction**  
The `variant` query parameter value is assembled as:  
```
variant = <prepend claim value>_<resolver-query-param from matching rule>
```
The `prepend` JWT claim is optional. If absent, only the `resolver-query-param` value is used. ECP sets `?variant=<value>` on the request URL, replacing any `variant` parameter the request already carried, or removing it when no variant applies. Editing the parameter in the URL has no effect.
7. **Request forwarded**  
The modified URL is passed to the CDN and PageBuilder for rendering. PageBuilder resolvers read the `variant` parameter and render the appropriate template.  
If no rule matches, the request is forwarded without a `variant` parameter and the default template is rendered.

## Invalid JWT Handling

When JWT validation fails (bad signature, expired `exp`, missing `entitlements` claim), one of two behaviors applies:

- **Redirect URL configured**: the browser is redirected to your system to obtain a new cookie, then back to the original page. See [Cookie Lifecycle](#cookie-lifecycle) for the full refresh flow.
- **No redirect URL configured**: the request is forwarded to the CDN/PageBuilder without a `variant` parameter, and the default template is rendered.

## JWT Format

You generate and set the JWT cookie in your own external system. The JWT must be signed with HS256 or HS512 using the secret configured in the Delivery admin UI.

| Claim        | Required | Description                                                                       |
| ------------ | -------- | --------------------------------------------------------------------------------- |
| exp          | Yes      | Expiration timestamp (Unix epoch). Validated by ECP.                              |
| entitlements | Yes      | Comma-separated list of entitlement strings.                                      |
| prepend      | No       | String prepended to the resolver-query-param value when constructing the variant. |

### Sample JWT Payload

```json
{
  "entitlements": "subscriber,tier_gold",
  "prepend": "us",
  "exp": 1763035200
}
```

With this payload, suppose a rule whose `entitlements-pattern` matches `"subscriber,tier_gold"` has `"resolver-query-param": "premium"`. The resulting query parameter is `?variant=us_premium`. The `entitlements` value is used only for pattern matching — it is the rule’s `resolver-query-param` that determines the variant.

> **Note:**
> 
> You can include any additional claims in the JWT. ECP ignores unrecognized claims and does not treat their presence as an error. For example, you might embed the subscriber’s ID in your own system (e.g. `"publisher_sub_id": "user_12345"`) to support server-side logging or personalization downstream — ECP passes the request through without inspecting that value. You can add any other claims your own system needs when it reads the cookie.
> 
> Standard JWT claims (`iss`, `sub`, `aud`, `iat`, `nbf`) are also not validated by ECP.

## Cookie Lifecycle

You set and refresh the JWT cookie; Arc does not issue, modify, or renew it. You control when and how the cookie is written — typically after a user signs in or completes a subscription purchase in your own system.

### Expiration and Refresh

The `exp` claim in the JWT determines how long the cookie remains valid before ECP rejects it. You choose this value, and the choice involves a trade-off:

| exp strategy     | Effect                                                                                                                                                                                                                                                      |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Long expiration  | Cookie is refreshed infrequently. Entitlements in the JWT may become stale — for example, a subscriber who cancels may continue to receive premium content until the cookie expires.                                                                        |
| Short expiration | Cookie is refreshed frequently, keeping entitlements current. However, each expiry triggers a redirect cycle: Arc redirects the user to your system to obtain a new cookie, then back to the original page. Frequent redirects degrade the user experience. |

Choose an expiration window that balances entitlement accuracy against the disruption of redirect-based refreshes.

### Refresh Flow

When a JWT expires or is otherwise invalid and a redirect URL is configured, the following flow occurs:

1. The user’s request arrives at the edge with an expired cookie.
2. ECP rejects the JWT and redirects the browser to your configured redirect URL.
3. Your system authenticates the user, issues a new JWT, and sets a fresh cookie.
4. Your system redirects the browser back to the original page.
5. ECP validates the new cookie and applies the appropriate variant.

If no redirect URL is configured, expired cookies are silently treated as absent — the user receives the default template with no redirect.

> **Danger:**
> 
> **Infinite redirect loops**
> 
> If your system issues a new cookie that ECP also rejects — for example, because the cookie is malformed, signed with the wrong secret, or still expired — the redirect cycle repeats indefinitely: ECP rejects the cookie, redirects to your system, your system sets the same bad cookie and redirects back, and so on.
> 
> Arc’s Web Application Firewall (WAF) detects browsers caught in rapid redirect loops and will eventually block them. **Arc has no way to clear this block** — the affected browser must wait for the WAF block to expire on its own. Ensure your redirect endpoint always issues a valid, correctly signed JWT before redirecting back to the original page.

## Rules Document

The rules document is a JSON file uploaded in the Delivery admin UI. It contains a prioritized list of matchers. Rules are evaluated in ascending priority order; the first rule that matches both the request URL and the `entitlements` claim is used.

### Regular Expression Engine

Both `uri-pattern` and `entitlements-pattern` are evaluated using the [RE2](https://github.com/google/re2) regular expression engine. RE2 is designed for high-throughput matching at the edge and guarantees linear-time execution regardless of input size. The trade-off is that RE2 does not support certain features available in other engines — notably backreferences and lookahead/lookbehind assertions.

The patterns are used for **matching**, not parsing. Capture groups are accepted in the syntax but their captured values are not used; only whether the overall pattern matches determines rule evaluation.

### Rule Fields

| Field                | Type    | Description                                                                                                                                               |
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| priority             | integer | Evaluation order. Lower numbers are evaluated first.                                                                                                      |
| uri-pattern          | string  | RE2 regular expression tested against the incoming request URL. The rule matches if the pattern matches anywhere in the URL unless anchored with ^ and $. |
| entitlements-pattern | string  | RE2 regular expression tested against the full entitlements claim string. The rule matches if the pattern matches anywhere in the value.                  |
| resolver-query-param | string  | The value used as (or appended to the prepend value to form) the variant query parameter.                                                                 |

### Sample Rules Document

```json
{
  "matchers": [
    {
      "priority": 1,
      "uri-pattern": "^(\\/sports\\/.*\\/[0-9]{4}\\/[0-9]{2}\\/[0-9]{2}\\/.*)$",
      "entitlements-pattern": "\\b(sports_ads)\\b",
      "resolver-query-param": "sports_ads"
    },
    {
      "priority": 2,
      "uri-pattern": "^(\\/sports\\/.*\\/[0-9]{4}\\/[0-9]{2}\\/[0-9]{2}\\/.*)$",
      "entitlements-pattern": "\\b(sports_no_ads|premium)\\b",
      "resolver-query-param": "sports_no_ads"
    },
    {
      "priority": 3,
      "uri-pattern": "^(\\/.*)$",
      "entitlements-pattern": "\\b(premium)\\b",
      "resolver-query-param": "premium"
    }
  ]
}
```

In this example:

- A reader with `entitlements: "sports_ads"` visiting a sports article URL matches rule 1 and receives `?variant=sports_ads`.
- A reader with `entitlements: "sports_no_ads,premium"` visiting the same URL matches rule 2 (rule 1 does not match their entitlements) and receives `?variant=sports_no_ads`.
- A reader with `entitlements: "premium"` visiting a non-sports URL skips rules 1 and 2 (URI pattern does not match) and matches rule 3, receiving `?variant=premium`.
- A reader with no matching entitlements receives no `variant` parameter and sees the default template.

## Configuration Parameters

These are configured in the Delivery admin UI under **Site settings → Edge Integrations → Content Protection**.

| Parameter      | Required | Description                                                                                                                                                                                                                                                                                                                                   |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cookie name    | Yes      | Name of the cookie that contains the JWT. Must match what your external system sets. The cookie’s value is the JWT, and ECP reads only the entitlements, prepend and exp claims inside it. Other cookie attributes have no effect on ECP. Set the cookie’s domain and path so the browser sends it to every host that serves protected pages. |
| HMAC secret    | Yes      | Secret used to validate the JWT signature. Must match the secret used to sign the cookie.                                                                                                                                                                                                                                                     |
| Rules document | Yes      | JSON file containing the prioritized list of matchers (see [Rules Document](#rules-document)).                                                                                                                                                                                                                                                |
| Include paths  | No       | ;\-separated wildcard patterns (\*, ?). If set, only requests whose path matches at least one pattern are processed by ECP; all others are forwarded as-is.                                                                                                                                                                                   |
| Exclude paths  | No       | ;\-separated wildcard patterns (\*, ?). Requests whose path matches any pattern bypass ECP processing entirely.                                                                                                                                                                                                                               |
| Redirect URL   | No       | URL to redirect users to when JWT validation fails. If not set, requests without a valid JWT are forwarded without a variant parameter.                                                                                                                                                                                                       |

## PageBuilder Integration

ECP works with PageBuilder resolvers. Each unique `variant` value must correspond to a PageBuilder template configured to handle it.

For more information, see [Configuring resolvers](https://docs.arcxp.com/en/products/pagebuilder/pagebuilder-editor/developer-tools-overview/configuring-resolvers.html).

## Cost Considerations

> **Caution:**
> 
> Each unique `variant` value results in a separately cached render in PageBuilder. There is no limit to the number of unique variants ECP can produce. To optimize rendering costs, keep the number of distinct `prepend` values you use low — each unique variant adds a separate cached render.

## Cache Clearing Compatibility

> **Caution:**
> 
> ECP is not compatible with Arc’s automated or manual cache clearing features. When ECP appends a `variant` query parameter to a URL, each variant produces a distinct cache entry — for example, `/article/` and `/article/?variant=premium` are cached separately. Arc’s cache purge tools, both automated and manual, do not account for these variant-appended URLs.
> 
> As a result, once a page is cached under a variant URL, it can only be expired by the CDN’s default TTL. See [Web experience cache timing](https://dev.arcxp.com/caching/overview/#web-experience-cache-timing-default-ttls) for the values.

## Migrating from Arc’s Server-Side Paywall

ECP is the successor to [Arc's Server-Side Paywall](https://dev.arcxp.com/subscriptions/developer-docs/how-to-build-a-server-side-paywall-with-arc-subscriptions-or-an-external-system/) (SSP). If you already run SSP, you can move to ECP gradually, with no cutover downtime, because both features run at the edge and SSP is evaluated _before_ ECP. For a conceptual comparison of the two, see [What is Edge Content Protection?](https://dev.arcxp.com/subscriptions/content-protection-metering/learn/what-is-edge-content-protection/).

### How the Two Features Coexist

Arc’s edge logic evaluates SSP first and ECP second, and both can be enabled at the same time. To let them run cleanly side by side during migration, **give ECP a different cookie name than your SSP cookie.** With distinct names, each reader is handled by whichever cookie is present on their request:

- A reader who still carries the SSP cookie (`entitlement.timestamp.hash`) is handled by SSP, exactly as before. ECP finds no JWT cookie and passes the request through untouched.
- A reader who has been issued the new ECP JWT cookie is passed through by SSP (which finds no SSP cookie) and handled by ECP.

Using the same name for both would force SSP to attempt to parse the JWT as an `entitlement.timestamp.hash` value and vice versa, so keep them separate until SSP is fully retired.

Because the two run side by side, you can migrate readers in place — as cookies are reissued, traffic shifts from SSP to ECP with no coordinated cutover.

### Recommended Approach: Migrate the Cookie First, the Rules Later

Migrate in two phases so that no PageBuilder work is required up front.

**Phase 1 — Switch the cookie format, keep everything else the same.**

Keep your existing PageBuilder templates and resolvers untouched and change only the cookie your middleware issues:

1. Update your middleware to set an ECP JWT under a **new, dedicated cookie name** (distinct from your SSP cookie) instead of the SSP `entitlement.timestamp.hash` cookie. This is not a value-format tweak — you must emit a real signed JWT (see [JWT Format](#jwt-format)), not an HMAC over a dotted string. Keeping the SSP cookie name reserved for SSP lets both features run without interfering with each other during the rollout.
2. Configure an ECP rules document whose `resolver-query-param` values **reproduce the `variant` values your current resolvers already expect** (for SSP these are `1` and `2`), with no `prepend`. ECP then produces the same `?variant=1` / `?variant=2` URLs SSP produced, so your existing templates render unchanged.
3. Roll the middleware change out. As readers’ cookies are reissued on their next sign-in or revalidation, they move from SSP handling to ECP handling transparently.

**Phase 2 — Adopt ECP’s new capabilities.**

Once effectively all readers carry an ECP JWT, begin taking advantage of what ECP adds over SSP:

- Expand the `entitlements` claim from two buckets to any number of arbitrary strings.
- Add per-URL `uri-pattern` rules, priority ordering, and include/exclude paths instead of a single site-wide rule.
- Introduce new `variant` values (and matching PageBuilder templates), optionally using `prepend`, for additional reader experiences.

Retire SSP once no meaningful traffic still depends on it.

### Concept Mapping

| SSP                                                                              | ECP                                                                                                                                             |
| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Cookie value entitlement.timestamp.hash                                          | Signed JWT (HS256 or HS512) with entitlements, exp, and optional prepend claims                                                                 |
| Entitlement is the integer 1 or 2                                                | entitlements is a comma-separated list of arbitrary strings                                                                                     |
| HMAC-SHA256 over entitlement.timestamp, signed with a TAM-provided shared secret | Standard JWT signature verified with the HMAC secret configured in the Delivery admin UI                                                        |
| Cookie name and shared secret assigned by your Technical Account Manager         | Cookie name, HMAC secret, and rules document configured by you in the Delivery admin UI                                                         |
| Two entitlement buckets, applied site-wide                                       | Unlimited entitlements, matched per-URL by a priority-ordered rules document                                                                    |
| variant \= the entitlement integer, or absent when no cookie is present          | variant \= prepend \+ the matching rule’s resolver-query-param                                                                                  |
| Revalidation timestamp embedded in the cookie (must be < 90 days out)            | exp claim (Unix epoch)                                                                                                                          |
| Expired cookie → CDN 302 to your returnUrl login/revalidation endpoint           | Invalid or expired JWT → redirect to the configured [Redirect URL](#configuration-parameters), following the same [refresh flow](#refresh-flow) |

### Migration Checklist

1. **Provision ECP.** Submit a ticket through the [Arc XP Customer Support (ACS) portal](https://arcpublishing.atlassian.net/servicedesk/customer/portal/2) to enable ECP for your organization.
2. **Inventory your SSP setup.** Record the current cookie name and shared secret, your two entitlement buckets and the resolver/template each maps to, your middleware endpoints, and your login/revalidation (`returnUrl`) endpoint.
3. **Configure ECP to mirror SSP.** In the Delivery admin UI, set a new ECP cookie name (distinct from your SSP cookie), the HMAC secret, and the Redirect URL, and upload a rules document whose `resolver-query-param` values match the `variant` values your existing resolvers already handle.
4. **Update middleware to issue a JWT.** Emit a JWT containing `entitlements` and `exp` (and `prepend` only if you need it), signed with the ECP HMAC secret, under the new ECP cookie name — leaving the SSP cookie name untouched so SSP continues to serve readers who have not yet migrated.
5. **Verify parity.** In a test environment, confirm that each reader bucket produces the same `variant` — and therefore the same rendered template — under ECP as it did under SSP.
6. **Roll out and let cookies age over.** Deploy the middleware change; readers migrate from SSP to ECP as their cookies are reissued.
7. **Evolve, then decommission.** After the migration window, expand your rules and templates to use ECP’s richer capabilities, then retire SSP.

> **Caution:**
> 
> During Phase 1, keep your ECP `resolver-query-param` values identical to your existing SSP variant values (`1` and `2`). Changing them at the same time as the cookie swap would require simultaneous PageBuilder resolver changes and defeats the purpose of a low-risk, cookie-only first phase.

## Security and Best Practices

- Use a strong HMAC secret (minimum 32 characters recommended) to prevent JWT forgery.
- Set short `exp` values in JWTs and rotate secrets periodically.
- Design `entitlements-pattern` expressions to be as specific as possible to avoid unintended rule matches.
- Keep the number of unique `variant` values low to control PageBuilder rendering costs.
- Use the redirect URL to direct users with invalid or expired JWTs to a sign-in or upgrade page.
- Ensure your redirect endpoint always issues a valid, correctly signed JWT before redirecting back to Arc. A redirect endpoint that repeatedly produces an invalid cookie triggers Arc’s WAF loop protection, blocking the affected browser until the WAF block expires — Arc has no way to clear this block; it expires on its own.
