AEM, AWS CloudFront CDN and Closed User Groups (CUGs)

There any many ways to improve the performance of your Adobe Experience Manager (AEM) website. Using a CDN is the best way to improve the performance and reduce cost on the number of servers you need, particularly if you rely heavily on assets (images, videos, files).

A common use of a CDN is to put your entire website and assets behind it. But what if you need to have authentication on part of your website or assets? What if you want to use Closed User Groups (CUGs)? Things get more complicated, particularly if some groups have different access rights on different parts of your site.

I am going to show you a possible solution for this problem.

AWS CloudFront CDN

As most CDNs, CloudFront supports signed cookies or signed URLs if you want to control access to your content. However, this only assumes that all authenticated users have access to all content. What happens if you want to limit access to some content based on the user group?

Closed User Groups (CUGs) are used in AEM to limit access to parts of your content or assets. I am going to show you how you can integrate this with CloudFront by using Lambda@Edge functions.

Permission Sensitive Cache Servlet

CloudFront will query AEM to check wether the user has access to the resource. ACS AEM Commons already provides a servlet capable of doing that – make sure you already have the ACS Commons package installed on your instance.

When we send a HEAD request to this servlet it will return a 200 response if the user has access and 401-Forbidden if the user doesn’t. In this request we need to include the login token, so that AEM can identify the user.

For setting up the Permissions Sensitive Cache Servlet, follow the steps:

  1. Create OSGI configuration on your publish server
 <?xml version=”1.0" encoding=”UTF-8"?><jcr:root xmlns:sling=”http://sling.apache.org/jcr/sling/1.0" xmlns:jcr=”http://www.jcp.org/jcr/1.0" jcr:primaryType=”sling:OsgiConfig” sling.servlet.paths=”[/bin/permissioncheck]”/>
  1. Update your dispatcher configuration to use it
/auth_checker
{
/url “/bin/mysite/wcm/permissioncheck”
/filter
{
/0000
{
/glob “*”
/type “deny”
}
/0001
{
/glob “*.html”
/type “allow”
}
}
/headers
{
/0000
{
/glob “*”
/type “deny”
}
/0001
{
/glob “Set-Cookie:*”
/type “allow”
}
}
}
  1. Update dispatcher configuration to not cache HEAD requests to /bin/mysite/wcm/permissioncheck

Lambda Function

For the Lambda@Edge function you will need to get the login token from the ‘login-token’ cookie present on your request:

function parseCookies (request) {
var list = {},
rc = request.headers.cookie;
rc && rc.split(‘;’).forEach(function( cookie ) {
var parts = cookie.split(‘=’);
list[parts.shift().trim()] = decodeURI(parts.join(‘=’));
});
return list;
}exports.handler = (event, context, callback) => {
...
const loginToken = parseCookies(request)[‘login-token’];
...
}

Then create a HEAD request to call the servlet in AEM:

const options = {
method: 'HEAD',
host: baseUrl,
port: 443,
path: templateUrl,
headers: {'Cookie': 'login-token=' + loginToken}
};
var req = https.request(options, function(res) {
if(res.statusCode === 200){
callback(null, request);
} else {
if(loginToken){
// if login token not present and forbidden, then display forbidden page
request.uri = forbiddenPage;
callback(null, request);
} else {
//redirect to login if login token not present
callback(null, getLoginResponse());
}
}
}
).on('error', (err) => {
// if an error occurs, display error page
request.uri = errorPage;
callback(null, request);
}).end();

The code above calls the servlet, including the login-token as part of the request. If the response is 200 OK, then it will continue displaying the requested page. If not it will either redirect to the login page, if login token is not present, or display the forbidden page.

Conclusion

There will still be HEAD request sent to the AEM publisher(s), and you will need to scale you publishers to handle your website traffic. It will however reduce the load of transferring webpages, CSS, JS, video, images and other files.

Furthermore, this solution can be updated, to limit the HEAD requests to the server, by defining a path on your website that needs to be secured. e.g. /content/myweb/secure/*

Do you have any comments or suggestions? Do you have a better solution for this problem? Please leave comments below.

Leave a Reply