Support Vanity URL for authentication


Closed User Groups (CUGs) are used to limit access to specific pages that reside within a published internet site. These pages require authentication to access content.

Lets go little deep to understand how it works internally.

When author assign a page to closed user group, system adds a node property cq:cugEnabled to page to indicate that this page requires authentication.

Service on publish instance looks for any page with CUG property enabled and adds absolute page url to sling.auth.requirements property.  Any OSGi service may provide a sling.auth.requirements registration property which is used to dynamically extend the authentication requirements from the Authentication Requirements configuration. Following is example of page url added to Authentication Requirements configuration with auth required = true.

vanity url

When you try to access this page in publisher with absolute url, SlingAuthenticator looks at sling.auth.requirements to know if any URL requires authentication. It prompts user to input his credentials if he is already not logged in. However Authentication works only when you access page with absolute URL. Authentication does not work if you try to access page with vanity URL. Simple reason for it is that vanity Urls are not added to Authentication Requirements configuration.
Solution is to have your own service to add vanity URL to sling.auth.requirements property based on cq:cugEnabled associated to page.

Following is custom service which first adds vanity URLs of all authorized pages on initialization. It registers event listener to listen to page activation/deactivation event. In event listener vanity URL is added or removed based on cq:cugEnabled property associated to page.

@Component(metatype = true, immediate = true, label = “Custom Closed User Group”, description = “Custom Closed User Group Support”)
@Property(name = “service.description”, value = { “Custom Closed User Group Support” }),
@Property(name = “event.topics”, value = { “org/apache/sling/api/resource/ResourceResolverMapping/CHANGED” }, propertyPrivate = true) })
public class CustomCugAuthHandlerImpl implements CustomCugAuthHandler, EventListener, EventHandler {

@Property(name = “cug.enabled”, boolValue = { true },description=”Add vanity URL of page to closed group authentication”)
private boolean cugEnabled;

private ResourceResolverFactory resourceResolverFactory;

private final Logger logger = LoggerFactory.getLogger(CustomCugAuthHandlerImpl.class);
private ResourceResolver resolver;
private Map properties;
private ServiceRegistration registration;
private final Map cugRoots = new HashMap();

private void activate(BundleContext bundleContext, Map properties) {
cugRoots.clear(); = properties;
try {
this.resolver = this.resourceResolverFactory.getAdministrativeResourceResolver(null);
configure(bundleContext, properties);

} catch (LoginException le) {
this.logger.error(“activate: Cannot get an administrative ResourceResolver; CUG support disabled”, le);

private void configure(BundleContext bundleContext, Map properties) {

this.cugEnabled = OsgiUtil.toBoolean(properties.get(“cug.enabled”), false);
if (this.cugEnabled) {

this.registration = bundleContext.registerService(“com.wcm.auth.service.CustomCugAuthHandler”,
this, getProperties());

private void findInitialSet() {
String queryString = “select * from nt:base where cq:cugEnabled=’true'”;
try {
Session session = (Session) this.resolver.adaptTo(Session.class);
QueryManager qm = session.getWorkspace().getQueryManager();

Query q = qm.createQuery(queryString, “sql”);
NodeIterator nodes = q.execute().getNodes();
while (nodes.hasNext()) {
Node node = nodes.nextNode();
if (“jcr:content”.equals(node.getName())) {
node = node.getParent();

// Add vanity url to cugRoot

} catch (RepositoryException re) {
this.logger.error(“findInitialSet: Failed finding initial set of CUG roots”, re);

private Dictionary getProperties() {

logger.debug(“Get auth properties——” + this.cugRoots.size());

Hashtable newProps = new Hashtable();
for (String key : {

Set authReqs = new HashSet();
for (CugRoot cugRoot : this.cugRoots.values()) {
if (cugRoot.getVanityUrl() != null) {

logger.debug(“sling.auth.requirements=” + authReqs);
newProps.put(“sling.auth.requirements”, authReqs.toArray(new String[authReqs.size()]));

return newProps;

/* Register event handler to listen to all page activation/deactivation events
public void onEvent(EventIterator events) {
// Write logic here to add or remove page vanity url from Authorization Requirement configuration based on cugEnabled property



Leave a Reply

Your email address will not be published. Required fields are marked *