Org Skills
Referenceaem-security

Permissions, service users and protected content

Supporting material for aem-security. Agents load it on demand; it ships inside the skill folder.

RawSource

Verified 2026-09 against https://experienceleague.adobe.com/en/docs/experience-manager-learn/cloud-service/developing/advanced/service-users and https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/security/home.

repoinit service users

On AEM as a Cloud Service, service users live under system/cq:services and are granted principal-based ACLs. This is the form to use:

create service user acme-price-sync with forced path system/cq:services/acme

set principal ACL for acme-price-sync
    allow jcr:read on /content/acme
    allow jcr:read on /content/dam/acme
end

A job that must write gets the write on exactly the subtree it writes:

create service user acme-import with forced path system/cq:services/acme

set principal ACL for acme-import
    allow jcr:read on /content/acme
    allow jcr:read,rep:write on /var/acme/import
end

Notes:

  • with forced path creates the intermediate nodes and puts the user in the right place; without it you may get a user in a location that principal-based ACLs do not cover.
  • The older with path system/<project> + set ACL form still parses, but it is not the Cloud Service recommendation and mixes two authorization models across a codebase.
  • Bind the user in the OSGi factory config for ServiceUserMapped, and resolve with getServiceResourceResolver — one subservice name per job.
  • Always close the resolver (try-with-resources). A leaked resolver is both a leak and a long-lived privileged session.

Group and permission design

  • Grant to groups, never to users. Groups map from Adobe IMS product profiles in the Admin Console.
  • Grant on as few nodes as possible, as high in the tree as correctness allows. Deep per-folder grants are slow to evaluate and impossible to audit.
  • Prefer allow-only. A deny that overrides an inherited allow is where permission bugs hide.
  • Author, publish and preview are separate: a group that can publish is a different group from one that can edit.

Closed User Groups

A CUG restricts a content subtree and sends unauthenticated visitors to a login page.

  1. Apply the CUG to the root of the protected subtree.
  2. Name the group(s) allowed to read it.
  3. Set the login page.
  4. Confirm the dispatcher does not cache the protected paths as though they were public.

Test it with a fresh browser profile. A CUG that only appears to work because you are already authenticated as an author is the most common false pass.

Permission-sensitive caching

Protected pages are uncacheable by default, which is why the dispatcher offers permission-sensitive caching: the page body is cached once, and each request is authorised before it is served.

  • The dispatcher is configured with an auth checker that calls AEM to ask whether this session may read this path, and caches the answer briefly.
  • The URL it calls must be permitted by the dispatcher filters, or every request fails closed.
  • Verify with two users in different groups, and an anonymous visitor, against the same URL.

The alternative, which is simpler and usually faster: keep the page public and cacheable, and load the personalised or protected portion client-side with an authenticated request. Choose this unless the page as a whole must be hidden.

What to check in review

  • Is there a service user with more than one job's worth of privilege?
  • Does any grant use jcr:all, or apply at /?
  • Is any ACL granted to a user rather than a group?
  • Does any cached path render content that varies by user?
  • Is every resolver closed on every path, including error paths?