Technical

AEM Feature – Author Preview Servlet

Riccardo Annandale 7e2pe9wjl9m Unsplash

Why is this needed?

It may just be me, but I’ve found it incredibly common for clients to request an unauthenticated URL to view in-progress pages within an AEM Author instance.  The ask makes a lot of sense, as often those who approve content will often just want to give a cursory glance, and not need extensive time within the AEM Author system or a dedicated login/account.  Instead, it’d be great to have a way to preview content prior to go-live, potentially via an offline or email based approval process.  Today, that is not available out of the box with AEM, and people wanting to have this behavior have either a dedicated “Preview” Publish instance, or alternatively, have to bite the bullet and forced all their approvers to log in prior to seeing any content.

A new challenger approaches

Enter the Preview Servlet.  This custom servlet bypasses authentication and allows a page to render directly on the active author instance.  How, you ask?  A little trick within the Sling Servlet specification to remove authentication on the servlet request, coupled with a separate apache VHost configuration used to force all traffic requested to go through our preview servlet. (Ideally, this is done via a separate, specific sub-domain)

Now, let’s break down how this servlet works.

 

The Preview Servlet

The servlet itself is quite simple.  This servlet will listen on given endpoint, and from the passed in suffix, determine what content to render.  For example, using our default endpoint, preview.html, we could render the we-retail root page with the following:

/preview.html/content/we-retail/us/en/men.html

The above will look very familiar to those who work within the AEM author UI, as it leverages the same concept on using suffixes. Similar to the authoring UI, we use the suffix to determine the page that is being referenced.  However, there is one key difference, in that the out of the box services leveraging suffixes are utilizing the initial users session/authentication, while we’ll need to have our service available to unauthenticated users.

Fortunately for us, there is a sling property to remove authentication requirements on an exposed servlet.  The property is defined as follows:

sling.auth.requirement=-/preview

Of course, as a general practice you’d likely want to use a static variable to set this instead of a hard-coded string, so the definition will look something like the following:

@Component(

    service = {Servlet.class, Filter.class},

    immediate = true,

    property = {

      Constants.SERVICE_DESCRIPTION + "=" + "Preview servlet",

      ServletResolverConstants.SLING_SERVLET_METHODS + "=" + HttpConstants.METHOD_GET,

      ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES + "=" + "perficient/servlet/preview",

      ServletResolverConstants.SLING_SERVLET_EXTENSIONS + "=" + PreviewServlet.URL_SUFFIX,

      AuthConstants.AUTH_REQUIREMENTS + "=-" + PreviewServlet.URL_PREFIX

})

Great! Now we have a servlet which allows unauthenticated access and can read in the path of the page we want to render! However, we still aren’t doing anything with the path itself.  Now, the fun stuff – It’s time to get page content.  To do this, we can use another handy class, the RequestDispatcher.  This will take a provided resource node and dispatch the request for rendering.  OK, this is great!  However, we’re still in an unauthenticated state, so at this point we need to ensure we render the content as-if the user actually has read permissions.  This is the part where your security team gets irritated, and I need to give the following disclaimer:

**Disclaimers**

  1. If you are opening up your author site to anonymous readers, even if only read access, you need to be VERY careful on what paths you expose.  And as such, please be very, very, careful in assigning your service user permissions.
  2. Users accessing your anonymous endpoint for the purpose of review should be counted as an author user in terms of the AEM license.  This is not intended to allow more author users than the contract allows.

With the disclaimer out of the way, let’s proceed.

At this point we need to assign a service user to the request forwarded by request dispatcher.  To do this we use a relatively common strategy of creating a sling request wrapper and updating that wrapper to have a pre-configured service-user set on the resource resolver.  Said service user in our implementation is configurable via a property within a /conf.  Although not required, this is a method I highly recommend, especially for those in multi-tenant systems.

Here is a sample of the servlet wrapper and service user attachment logic here:

ResourceResolver resourceResolver = repoService.getResourceResolver(serviceUser);

Object oldRR = request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER);

    WCMMode mode = WCMMode.DISABLED.toRequest(request);

    try {

      request.setAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER, resourceResolver);

      SlingHttpServletRequestWrapper wrapper =

          new SlingHttpServletRequestWrapper(request) {

            @Override

            public ResourceResolver getResourceResolver() {

              return resourceResolver;

            }

          };

      RequestDispatcher dispatcher = request.getRequestDispatcher(newResource);

      dispatcher.forward(wrapper, response);
...

Notice also that we’re setting the WCMMode to disabled – this ensures everything will be rendered as if a publisher.

Time to test, right?  Unfortunately, we have missed an essential piece to the puzzle here.

What about the assets and internal links referenced on the page content?! 

When you request through the servlet endpoint ( /preview.html for this articles purposes), the links, as well as the associated assets will still be without the prefix, and thus, need authentication to view on author. How do we get around that?! Well, as most problems in development – there are a few ways to tackle this.  One possibility is to write a link rewriter to rewrite everything to resolve through the servlet, based on the servlet prefix.  This method, although possible, is difficult and finicky.  Instead, another option to create separate VHost in apache dedicated for preview requests.  This method has a few benefits over the rewriter:

  1. When you assign the preview servlet a dedicated domain, we can very easily adjust the passthrough url to always include /preview.html, and have the exposed link match your production servers
  2. No need to maintain two separate rewrite pipelines for the same content

The Apache VHost

At this point, we’re really close to having the preview solution we need – a single page’s markup can be viewed unauthenticated.  We just need to ensure that every request goes through our unauthenticated servlet, instead of just the initial page request.  Although this sounds complicated, this is actually the easy part!  All that remains is to create an apache VHost dedicated to your preview endpoint.  Although possible without a subdomain, it’s highly recommended here – an example:

http://preview-author.perficient.com/

Now, with one single mapping, we can easily force all traffic through our servlet!

Rewrite /(.*) /preview.html/(.*) [PT,L]

And then, just in case, I also HIGHLY suggest to block some of the base AEM links:

#301 aem interfaces to the site/asset they're looking for
RewriteCond ^/(sites|editor|assets).html
Rewrite /.+.html/(.*) /preview.html/$1 [R=301,NC,L]

#404 system only endpoints
RewriteCond ^/(aem|crx|system)
Rewrite .* - [R=404,L]

#Preview Traffic passthrough
Rewrite /(.*) /preview.html/(.*) [PT,L]

Of course, the above is for example purposes only – there are and will be more paths you want to restrict from a preview perspective.  With that said, it is best to ensure your service user has minimal permissions to minimize this risk.  Another important security step is to configure a whitelist, if at all possible, to only allow people from specific IP’s to use this functionality.

Next Steps

If you want to take things even further, I recommend to implement a button into the AEM “editor” interface to “Open in Author Preview”.  This will make the link generation and distribution even easier to evangelize.

 

Notes

The above will not work for AEM as a Cloud Service (AEMaaCS) deployments.  However, if you are working with AEMaaCS, Adobe has included some functionality to preview as seen here which leverages a separate instance.

 

Kudos

Credit to the servlet implementation and Proof of Concept goes to my talented friend Paul Bjorkstrand

Banner Photo by Riccardo Annandale on Unsplash

Happy coding, y’all.

About the Author

Multi Solution Architect with over a decade of experience in the Adobe stack.

More from this Author

Thoughts on “AEM Feature – Author Preview Servlet”

  1. Hi Paul, hi Ryan!

    this is a really cool feature, thanks for the posting! As you mentioned this is a recurring request with many customers, and I have discussed this internally a lot with my colleagues. And we always turned it down for these reasons:

    * A preview is nice, but a preview never stands alone. A preview is typically used as part of an approval workflow. That means someone previews and then accepts or rejects the content. But in that case preview is just half of the story, and you do the approval / rejection typically out-of-band, via email/phone/slack/… Also not good, because this feedback is not linked to the page itself and not recorded in the system. Having that all in an AEM workflow would be much better.

    * Security: Basically you disabled any security to protect content, because everyone who knows that pattern can access a LOT of your AEM authoring instance. You don’t even know who has accessed your new content before it has been made public.

    * License: If your AEM license has a restrictions regarding named users, one could argue that with this approach you are bypassing this restriction. Because you don’t need to create any accounts anymore for your reviewers.

    Our recommendation was always: Implement SSO, then your reviewers don’t need to login explicitly anymore, but you can give your reviewers directly a deep-link which opens the requested page in preview mode. Then they are authenticated, can approve or reject workflows, their access can be recorded for auditing, and there is not licensing problem.

    But: There are preview requirements which cannot be fulfilled by preview mode, and for this reason there is the preview environment in AEM as a Cloud Service. But that’s mostly because of personalization topics, which are real hard to solve on an authoring preview.

    Cheers,
    Jörg

  2. Ryan McCullough Post author

    Hey Jörg,

    Appreciate the thoughtful comment. I completely agree that in an ideal world, we’d be able to have the approvers or users of the preview functionality log in, and leverage workflow. That being said, in many of my clients in the past this has been a non starter for a variety of reasons, and hence the need for an unauthenticated viewpoint. In terms of the security issue, I tried to make that clear in the blog that it is extremely risky to expose content, and as such, both the service user permissions as well as (ideally) a whitelist to internal IPs or similar is added to the domain. This does not eliminate security concerns, but can help reduce them in the scenario that this is needed.

    In terms of licensing, that is a very interesting point. I don’t think the intention is to bypass that restriction at all, but can definitely see how it could be leveraged as such. I’ll add a note to the article calling out that users of this service should be counted towards any author user totals.

    Overall, I completely agree with the standard recommendation of SSO and deep-links, but this article is specifically for those situations where the client cannot, for whatever reason, utilize that approach. Many of our clients, for example, are fairly hesitant to grant SSO access to external organizations, such as the design/media agencies, who are required in their approval process. For those that choose not to add SSO accounts for these external users (which is fairly common occurrence) – they tend to fall back on legacy offline approaches. These are far more difficult and time consuming, as they primarily involve static renditions (PDF’s or screenshots) or screen-shares. In these cases where SSO is not a potential solve, does Adobe have another approach to close this gap?

    Cheers,
    Ryan

  3. Hi Ryan,

    so this solves the requirement, which has been solved before with sending screenshots via email? In such situations I understand that this approach can be a huge improvement already, but that still leaves the question of security open.
    If your customers don’t want to provide SSO access to AEM for their agencies, I also think that they don’t provide them VPN access. In that case your “preview” endpoint is world-wide open (as long as you know the URL).

    To answer your final question: I am not sure if that is a gap at all. Assuming that authentication and authorization is required, SSO is still the best and easiest way to solve it.

  4. Have you considered generating a JWT and sending that as a request parameter? With a JWT you could validate that:

    1. that the user has a valid sharing link by verifying the JWT signature against the server’s private key
    2. the user is accessing content they should be able to by passing a restriction path in the JWT body
    3. the sharing link has not expired by checking the expiry date

    You could either create a workflow or some UI element to share content, allowing the author to specify the page(s) to share and expiration date and then generate the share URL(s).

    This would be similar to the Ad-Hoc Asset Share feature in AEM Assets.

  5. Ryan McCullough Post author

    That is an amazing idea – Will certainly add to the list of version 2.0 features. Thanks for the feedback, Dan.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.