AEM in 40 Days
CalendarPhase 5 · Advanced Development

Day 32 of 40

Servlets, CSRF and CORS

Registering by resource type, and the request security model

~45 min read1 video3 source pages

By the end of today you should be able to

  1. Register a servlet by resource type and explain why that beats registering by path
  2. Explain AEM's CSRF protection and when it applies
  3. Configure CORS correctly without opening the site up
  4. Know when a custom JCR namespace is warranted

Servlets, and the right way to bind them

Sling offers two ways to register a servlet, and one of them is a security problem.

By resource type — preferred:

@Component(service = Servlet.class)
@SlingServletResourceTypes(
    resourceTypes = "wknd/components/page",
    selectors     = "adventures",
    extensions    = "json",
    methods       = HttpConstants.METHOD_GET)
public class AdventuresServlet extends SlingSafeMethodsServlet {

    @Override
    protected void doGet(SlingHttpServletRequest req,
                         SlingHttpServletResponse resp) { ... }
}

That responds to /content/wknd/us/en/home.adventures.json.

By path — avoid:

@SlingServletPaths("/bin/wknd/adventures")
Why path binding is discouraged

A path-bound servlet is reachable regardless of the ACLs on any content, so it sidesteps the repository's permission model entirely — you must reimplement authorisation yourself, and it is easy to forget. Resource-type binding means the user must already be able to read the resource. Path-bound servlets under /bin also need explicit Dispatcher filter handling.

Extend SlingSafeMethodsServlet for read-only servlets and SlingAllMethodsServlet when you genuinely need POST. The first makes the intent explicit and removes a whole class of mistakes.

CSRF protection

AEM ships a CSRF filter that requires a token on state-changing requests (POST, PUT, DELETE) to author. The token comes from /libs/granite/csrf/token.json, and Granite UI adds it automatically to forms and AJAX calls made through its framework.

You meet it when you write a custom POST outside that framework:

const { token } = await fetch('/libs/granite/csrf/token.json')
  .then(r => r.json());

await fetch('/bin/wknd/save', {
  method: 'POST',
  headers: { 'CSRF-Token': token },
  body: formData
});

Paths can be excluded from the filter via OSGi configuration, but doing so removes the protection for those paths — legitimate only for endpoints that are genuinely safe to call cross-origin, and worth a comment explaining why.

CORS

Cross-origin requests are blocked by the browser unless the server opts in. In AEM this is the Adobe Granite Cross-Origin Resource Sharing Policy, a factory configuration — you create one per policy:

{
  "alloworigin": ["https://app.example.com"],
  "allowedpaths": ["/content/wknd/.*\\.model\\.json"],
  "supportedmethods": ["GET", "HEAD"],
  "supportscredentials": false
}
  • Never use * for alloworigin in production. List the origins you actually serve.
  • Scope allowedpaths tightly — the specific endpoints, not the whole site.
  • supportscredentials: true permits cookies to be sent cross-origin. Combined with a wildcard origin it is a serious vulnerability, and browsers reject that combination for good reason.

Custom namespaces

JCR property and node names are namespaced: jcr:, sling:, cq:, dam:. You can register your own — wknd:campaignCode — via a namespace registration in your project.

It is rarely necessary. A plain unnamespaced property name is fine for component content, and a custom namespace adds registration and migration overhead. It earns its place when you are defining a genuinely new content model that must not collide with product properties, particularly if you also define custom node types.

Today's takeaway

Bind servlets by resource type so the repository keeps enforcing permissions. Respect CSRF rather than excluding paths. Scope CORS to real origins and real paths. Reach for a custom namespace only when you are defining a real content model.

Watch

Adobe's own videos for this topic. They load only when you press play.

Custom namespaces

Read on Experience League

The primary sources these notes are drawn from.

Your notes

Saved automatically to this browser.

Check yourself

10 questions on today's material. 80% to pass.

Take the quiz