Edge Content Protection tutorial: four-persona content access
This tutorial walks you through a complete Edge Content Protection (ECP) configuration for a site with two article sections and four distinct reader personas. It covers the JWT design, the rules document, and the PageBuilder resolvers and templates that must be created.
Scenario
In this scenario, you have two article sections with different access rules:
- News β free to all registered (authenticated) users.
- Sports β requires an active subscription entitlement.
Readers fall into one of four personas:
| Persona | Authentication | Entitlement | News access | Sports access |
|---|---|---|---|---|
| Anonymous | No account | None | Metered (limited) | Paywall |
| Registered | Signed in | None | Full article, with ads | Paywall |
| Subscriber β with ads | Signed in | subscriber_ads | Full article, with ads | Full article, with ads |
| Subscriber β no ads | Signed in | subscriber_no_ads | Full article, no ads | Full article, no ads |
Step 1: Design the JWT
ECP reads two custom claims from the signed JWT cookie you issue: entitlements and prepend.
In this scenario, prepend carries the authentication status signal. Every authenticated user β regardless of subscription tier β receives prepend: "A". When ECP constructs the variant query parameter, it concatenates prepend with the resolver-query-param from the matching rule. Anonymous users have no JWT cookie, so ECP appends no variant parameter at all.
JWT payloads
Registered user (authenticated, no subscription):
{ "prepend": "A", "entitlements": "registered", "exp": 1763035200}Subscriber β with ads:
{ "prepend": "A", "entitlements": "subscriber_ads", "exp": 1763035200}Subscriber β no ads:
{ "prepend": "A", "entitlements": "subscriber_no_ads", "exp": 1763035200}Anonymous: No JWT cookie is set. ECP skips rules evaluation entirely and forwards the request without a variant parameter.
Step 2: Build the rules document
The rules document tells ECP how to translate the entitlements claim into a resolver-query-param value. Because the distinction between news and sports access is enforced in the PageBuilder resolvers β not at the edge β the uri-pattern field is .* for all rules. ECP applies the same entitlement logic regardless of which article section is requested.
{ "matchers": [ { "priority": 1, "uri-pattern": ".*", "entitlements-pattern": "subscriber_no_ads", "resolver-query-param": "_subscriber_no_ads" }, { "priority": 2, "uri-pattern": ".*", "entitlements-pattern": "subscriber_ads", "resolver-query-param": "_subscriber_ads" }, { "priority": 3, "uri-pattern": ".*", "entitlements-pattern": ".*", "resolver-query-param": "_registered" } ]}Rules are evaluated in ascending priority order and processing stops at the first match:
entitlements: "subscriber_no_ads"β matches rule 1.entitlements: "subscriber_ads"β does not match rule 1 (nosubscriber_no_adsin value), matches rule 2.entitlements: "registered"β does not match rules 1 or 2, matches rule 3.
Rule 3 uses .* as a catch-all for any authenticated user whose entitlements do not include a subscription tier. This covers "registered" and any future entitlement string that does not match a higher-priority rule.
Variants produced
The final variant value is prepend + resolver-query-param. Because all authenticated users share prepend: "A":
| Persona | variant |
|---|---|
| Subscriber β no ads | A_subscriber_no_ads |
| Subscriber β with ads | A_subscriber_ads |
| Registered | A_registered |
| Anonymous | (none β no variant parameter appended) |
Step 3: Configure PageBuilder resolvers
A PageBuilder resolver matches an incoming URL, fetches article content, and selects the template to render. The resolver also reads the variant query parameter to determine which template to use. For more information on resolvers, see Configuring resolvers.
This scenario requires two resolvers β one per article section. The URL-based access distinction between news and sports is enforced here, not in the ECP rules document.
news-article-resolver
Matches URLs of the form /{year}/{month}/{day}/news/{slug}/.
variant | Template | Notes |
|---|---|---|
A_subscriber_no_ads | full-no-ads | Full article, no ad slots |
A_subscriber_ads | full-with-ads | Full article, with ad slots |
A_registered | full-with-ads | News is free for registered users; ad slots rendered |
| (absent) | anonymous-metered | Truncated article, soft paywall prompt |
sports-article-resolver
Matches URLs of the form /{year}/{month}/{day}/sports/{slug}/.
variant | Template | Notes |
|---|---|---|
A_subscriber_no_ads | full-no-ads | Full article, no ad slots |
A_subscriber_ads | full-with-ads | Full article, with ad slots |
A_registered | hard-paywall | Sports requires a subscription; upsell page rendered |
| (absent) | hard-paywall | Anonymous users also reach the paywall |
Step 4: Build your templates
Four templates are required.
full-no-ads
Renders the complete article body without ad slots. Used for A_subscriber_no_ads in both the news and sports resolvers.
full-with-ads
Renders the complete article body with ad slots. Used for A_subscriber_ads in both resolvers, and for A_registered in the news resolver. Registered users receive free access to news but are on an ad-supported tier.
anonymous-metered
Renders a truncated version of the article body followed by a soft paywall prompt. You control how much content is visible before truncation. Because anonymous users are only one step away from unlocking free news access, this template should include prominent registration and sign-in calls to action β directing anonymous readers to create an account or log in reduces friction and improves conversion.
hard-paywall
Renders no article content. The page presents a subscription upsell β typically a headline, description, and links to available subscription plans. Used for A_registered on sports (registered users must upgrade to access sports) and for anonymous users on sports.
End-to-end matrix
| Persona | News article | Sports article |
|---|---|---|
| Subscriber β no ads | full-no-ads | full-no-ads |
| Subscriber β with ads | full-with-ads | full-with-ads |
| Registered (no subscription) | full-with-ads | hard-paywall |
| Anonymous | anonymous-metered | hard-paywall |
JWT expiry and the redirect flow
JWT cookies expire based on the exp claim you set. When ECP encounters an expired or otherwise invalid JWT and a redirect URL is configured, the reader is redirected to your authentication system rather than silently falling back to the default template.
The flow for a registered or subscribed reader whose cookie has expired:
- The readerβs request arrives at the edge with an expired JWT cookie.
- ECP rejects the JWT and redirects the browser to your configured redirect URL.
- Your system authenticates the reader, issues a fresh JWT with current entitlements, and sets a new cookie.
- Your system redirects the browser back to the original article URL.
- ECP validates the new cookie and appends the correct
variantparameter.
Without a redirect URL configured, readers with an expired JWT are treated the same as anonymous users β they receive no variant parameter and see anonymous-metered (news) or hard-paywall (sports) until they sign in again manually.