August 27, 2026

Generate a Report of Users' Last Login in AEM

Sometimes you need a report of when each user last logged in to AEM. Many of the approaches published online do not work; the one below does, and it is tested on AEM 6.5.

Approach

An AuthenticationInfoPostProcessor service records the last login on each user's profile, and an ACS AEM Commons report reads it.

Step 1: deploy a service that records the last login

This Java class captures the user's last login and updates the user node. Adjust the conditions to your project's requirements.

package yourpackage.core.services;

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.jcr.Session;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.auth.core.spi.AuthenticationInfo;
import org.apache.sling.auth.core.spi.AuthenticationInfoPostProcessor;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(name = "UserProfileService", service = AuthenticationInfoPostProcessor.class, immediate = true)
public class UserProfileService implements AuthenticationInfoPostProcessor {

    /**
     * Records the last login time on the user's profile.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(UserProfileService.class);

    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    @Override
    public void postProcess(AuthenticationInfo info, HttpServletRequest request, HttpServletResponse response)
            throws LoginException {

        /*
         * The user's last login is their last active time in AEM.
         * Skip requests that should not update it, e.g. the logout request.
         * Update these conditions for your project.
         */
        if ((info != null && info.getAuthType() == null) || (request != null && request.getServletPath() != null
                && (/* your condition 1 */))) {
            LOGGER.debug("AuthenticationInfo is null, skipping post-processing for this request.");
            return;
        }

        ResourceResolver resourceResolver = null;
        Session session = null;
        UserManager userManager = null;
        Authorizable auth = null;

        try {
            resourceResolver = resourceResolverFactory.getResourceResolver(info);
            session = resourceResolver.adaptTo(Session.class);
            userManager = resourceResolver.adaptTo(UserManager.class);
            auth = userManager.getAuthorizable(session.getUserID());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
            // Anonymous users need not be recorded
            if (auth.getID() != null && (/* your condition 2 */)) {
                LOGGER.info("Recording user login");
                // The profile gets a new property
                auth.setProperty("profile/lastLoggedIn", session.getValueFactory().createValue(sdf.format(new Date())));
                session.save();
                session.logout();
            }
        } catch (Exception exception) {
            LOGGER.error("Could not record the last login", exception);
        }
    }
}

The conditions used here were:

// your condition 1
!request.getServletPath().equals("/system/sling/logout.html")
// your condition 2
!auth.getID().equals("anonymous")

Step 2: build the report in ACS AEM Commons

In ACS AEM Commons Reports, create a new report with a JCR-SQL2 query:

ACS Commons report builder
SELECT * FROM [rep:User] AS nodes WHERE ISDESCENDANTNODE("/home/users")
AND nodes.[profile/lastLoggedIn] IS NOT NULL
AND NOT ISDESCENDANTNODE([/home/users/community])
AND NOT ISDESCENDANTNODE([/home/users/mac])
AND NOT ISDESCENDANTNODE([/home/users/rep:policy])
AND NOT ISDESCENDANTNODE([/home/users/screens])
AND NOT ISDESCENDANTNODE([/home/users/system])

Configure the report columns like this:

Report text column
Report column configuration

The report now lists each user's last login:

Last login report in AEM