Protecting Content with Closed User Groups (CUG)

Learn how to restrict access to pages on your Edge Delivery site so that only authenticated users (optionally belonging to specific groups) can view them.

Overview

Closed User Groups (CUG) let you restrict access to pages on your site so that only authenticated users (optionally belonging to specific groups) can view them. You define the access rules in a spreadsheet, and when you apply them, the rules take effect at the CDN edge. The authoring step differs depending on whether your site runs on AEM Authoring or Document Authoring (DA), and the enforcement step differs depending on your CDN (Cloudflare or Akamai). This guide covers both.

Quick Start

Limiting access to pages using CUG starts with creating a spreadsheet and defining access rules in it. Once defined, the rules need to be applied so they become CUG headers that the CDN needs to enforce at the edge (see the section Applying Page Access below).

1. Create the CUG spreadsheet

Create a spreadsheet named closed-user-groups at your site root (the same level as your index page).

2. Add columns and rows

Example:

url cug-required cug-groups cug-login-path
/members/** true
/partners/** true gold,silver https://idp.partners.example.com/login

The spreadsheet has four columns. When the page access is applied, each row is translated into CUG headers:

Column Required Description CUG header
url Yes Path pattern to protect (must start with /)
cug-required No Whether login is required: true or false x-aem-cug-required
cug-groups No Comma-separated list of allowed groups x-aem-cug-groups
cug-login-path No Login page path (e.g. /login) or absolute URL (e.g. https://idp.example.com/login) to redirect unauthenticated visitors to x-aem-cug-login-path

Wildcard Patterns

Use wildcards in the url column to protect multiple pages at once:

Pattern Matches Does NOT match
/members/* /members/page1 /members/sub/page2
/members/** /members/page1, /members/sub/page2 /other/page

Examples

The examples below build on each other, using a hypothetical site with a members area, a partners area, and some internal pages.

Step 1: Require login for a section

Any authenticated user should be able to access pages under /members/, but visitors must be logged in.

url cug-required cug-groups cug-login-path
/members/** true

Any authenticated user can access pages under /members/.

Step 2: Restrict a section to specific groups

Now add a partners area that only the gold and silver groups should be able to reach.

url cug-required cug-groups cug-login-path
/partners/** true gold,silver

Only users in the gold or silver group can access pages under /partners/.

Step 3: Send a group to its own login page

Partners should log in through their own identity provider instead of the default login flow.

url cug-required cug-groups cug-login-path
/partners/** true gold,silver https://idp.partners.example.com/login

Only users in the gold or silver group who have logged in via https://idp.partners.example.com/login can access pages under /partners/.

Putting it all together

Combining tiered access within /members (separate gold and silver sub-groups, plus a free sub-section with authentication explicitly disabled), the partners rule from steps 2–3 (including its custom cug-login-path), and an internal employees-only area gives the following sheet:

url cug-required cug-groups cug-login-path
/members/** true
/members/gold/** true gold
/members/silver/** true silver
/members/free/** false
/partners/** true gold,silver https://idp.partners.example.com/login
/internal/** true employees

These mixed levels translate to the following behavior.

Visitor requests… Result
/members/page Login required, any authenticated user
/members/gold/page Login required, must be in gold group
/members/silver/page Login required, must be in silver group
/members/free/page Public (auth explicitly disabled)
/partners/page Login required via idp.partners.example.com, must be in gold or silver group
/internal/docs Login required, must be in employees group
/public/page Public (no CUG rule applies)

Inheritance

Child pages inherit the access rules of their parent path. For example:

Important Rules

Applying Page Access

Applying the page access defined in the closed-user-groups sheet translates each row into CUG headers and sends them to the Config Service. The CDN needs to enforce them on every request. How you apply page access depends on your authoring platform:

For AEM Authoring

Applying page access is a built-in, out-of-the-box capability. No custom tooling is required. Simply publish the closed-user-groups sheet as you would any other page.

Note: Publishing the CUG sheet does not make it visible on Edge Delivery Services as a regular sheet. The sheet content (i.e., closed-user-groups.json) is not accessible on preview or live. Only the derived CUG headers are applied to the Config Service.

For Document Authoring (DA)

Document Authoring (DA) supports applying page access via the CUG plugin, an Adobe-supported AEM app. Once installed, it reads the closed-user-groups sheet, transforms each row into the corresponding x-aem-cug-required / x-aem-cug-groups / x-aem-cug-login-path headers, and posts them to the Config Service.

To use it in your project, add the plugin to the prepare sheet of your project config:

title path icon
Protected Pages https://main--aem-apps--adobe-rnd.aem.live/tools/plugins/cug/cug.html https://da.live/img/icons/s2-icon-key-20-n.svg#icon

The plugin then appears in your project's DA sidebar, letting you apply or remove page access for the closed-user-groups sheet directly from the authoring UI. See Removing Page Access below for the removal side.

Removing Page Access

When you remove page access defined in the closed-user-groups sheet:

  1. All CUG headers are removed from the Config Service for your site.
  2. Every previously protected page becomes publicly accessible.
  3. Non-CUG headers (e.g., CORS headers) are preserved. Only the x-aem-cug-required, x-aem-cug-groups, and x-aem-cug-login-path headers are removed.

To re-enable protection, simply apply the page access again.

Warning: Removing page access affects the entire site at once. There is no way to selectively remove protection for individual paths. Either all rules are active or none are. To remove protection for a specific path, delete or modify that row in the sheet and re-apply page access.

For AEM Authoring

Removing page access is a built-in, out-of-the-box capability. No custom tooling is required. Simply unpublish the closed-user-groups sheet as you would any other page.

For Document Authoring (DA)

For Document Authoring (DA), removing page access is handled by the same CUG plugin referenced under the section Applying Page Access above. Use it to remove page access directly from the authoring UI.

CDN Requirement

CUG headers are set by AEM or DA in the Config Service, but enforcement happens at the CDN edge. Your CDN must have an edge worker (or equivalent) configured to:

  1. Read the x-aem-cug-required header. If it is true, verify the visitor is logged in.
  2. Read the x-aem-cug-groups header. If it is present, verify the visitor belongs to at least one of the listed groups.
  3. Read the x-aem-cug-login-path header. If it is present, redirect unauthenticated visitors to that path instead of a default login page.
  4. Redirect unauthenticated visitors to a login page, or return a 403/401 for unauthorized visitors.
  5. Strip the x-aem-cug-* headers before the response reaches the browser. They must never leak to the client.

The reference and sample implementations below cover Cloudflare and Akamai. If you're on a different CDN, adapt the same pattern to your platform's edge-worker equivalent.

Edge Enforcement on Cloudflare

Reference implementation: adobe-rnd/aem-cloudflare-worker-with-cug is a minimal starter template for Cloudflare Workers. It strips the x-aem-cug-* headers before they reach the browser, exposes a single isAuthorized(request, env, allowedGroups) extension point for plugging in your identity provider (it returns 401 by default until you implement it), and sets Cache-Control: private, no-store on authorized responses.

For a full working implementation, two sample workers are available:

  1. Sample implementation: aemsites/summit-portal/workers/cloudflare/cug-adobe-oauth-worker: This is a complete Adobe IMS OAuth 2.0 + PKCE login flow, with a signed JWT session cookie and KV-backed PKCE state. Groups are derived from the visitor's email domain.
  2. Sample implementation: aem-edge-cug/workers/cloudflare/frescopa-b2b-worker: This uses the same CUG header enforcement logic, but using a simple username/password login instead of Adobe IMS. This is useful as a starting point if you're not using SSO.

Edge Enforcement on Akamai

Sample implementation: aem-edge-cug/workers/akamai/cug-akamai-worker is a sample EdgeWorker implementing the same Adobe IMS OAuth 2.0 + PKCE flow as the Cloudflare sample above.

Note the key architectural difference from Cloudflare: an Akamai EdgeWorker's httpRequest can only reach hostnames served by the same Akamai property, so the IMS token exchange must be routed through a Property Manager rule rather than called directly. PKCE state is carried in a signed JWT state parameter instead of a key-value store.

Troubleshooting

Problem Solution
Protection not applied after editing the sheet Make sure you applied the page access defined in the closed-user-groups sheet
Sheet is ignored / has no effect Verify the sheet is at the site root (same level as index)
Specific rows have no effect Check that the url starts with / and is not a duplicate
All content became public The page access defined in the closed-user-groups sheet may have been removed. Re-apply it.
Protected pages still accessible Verify your CDN edge worker is configured and reading the x-aem-cug-* headers
Edited a row but the change has no effect Check whether that url appears in an earlier row too. Only the first occurrence of a duplicate path is used

Getting Help

Please reach out to your Adobe team on Slack or Teams for assistance.