Custom Authentication Handler in AEM

(Image source:Bobby Mavrov)
In Adobe Experience Manager (AEM), implementing secure customer authentication is crucial for managing user access to your web applications. Customer authentication handlers play a vital role in this process, enabling secure authentication and authorization mechanisms. In this blog post, we’ll delve into the implementation of a customer authentication handler in AEM, complete with code example to guide you through the process.
Understanding Authentication Handlers
Authentication handlers in AEM are responsible for authenticating users based on various criteria such as credentials provided via HTTP headers, request parameters, or cookies. They intercept incoming requests, extract authentication information, and validate user identities against configured authentication providers.
Creating the Custom Authentication Handler
To create a custom authentication handler in AEM, we’ll implement the AuthenticationHandler
interface provided by the Sling authentication framework. This interface defines methods for extracting credentials, handling successful and failed authentication attempts, and managing user sessions.
package com.MyCompany.aem.dotcom.core.models.components.application;
import com.MyCompany.aem.dotcom.core.models.components.marketing.RedirectPageUtil;
import com.MyCompany.aem.dotcom.core.models.components.marketing.MyCompanyLoginConfigService;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.auth.core.AuthUtil;
import org.apache.sling.auth.core.spi.AuthenticationHandler;
import org.apache.sling.auth.core.spi.AuthenticationInfo;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Credentials;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Set;
import java.util.UUID;
@Component(service = AuthenticationHandler.class, immediate = true, property = {
AuthenticationHandler.PATH_PROPERTY + "=/",
Constants.SERVICE_DESCRIPTION + "=Custom Authentication Handler",
"service.vendor=MyCompany.com"
})
public class LoginAuthenticationHandler implements AuthenticationHandler,
AuthenticationFeedbackHandler {
@ObjectClassDefinition(name = "MyCompany Token Authentication Handler")
public @interface Configuration {
@AttributeDefinition(name = "Paths")
String[] paths() default {"/"};
}
protected static final Logger LOG = LoggerFactory.getLogger(LoginAuthenticationHandler.class);
private static final String USER_COOKIE = "email";
private static final String USER_NAME = "j_username";
private static final String PASSWORD = "j_password";
private static final String REQUEST_URL_SUFFIX = "/j_login_check";
private static final String USER = "user.name";
private static final String REQUEST_METHOD = "POST";
private static final String LOGIN_TOKEN = "login-token";
@Reference
private SlingSettingsService slingSettings;
@Reference
private SlingRepository slingRepository;
@Reference
private MyCompanyLoginConfigService MyCompanyLoginConfigService;
private void activate(Configuration config) {
repositoryId = slingRepository.getDescriptor(REPO_DESC_CLUSTER_ID);
if (StringUtils.isBlank(repositoryId)) {
repositoryId = slingRepository.getDescriptor(REPO_DESC_ID);
}
if (StringUtils.isBlank(repositoryId)) {
repositoryId = slingSettings.getSlingId();
}
if (StringUtils.isBlank(repositoryId)) {
repositoryId = UUID.randomUUID().toString();
}
@Override
public AuthenticationInfo extractCredentials(HttpServletRequest request, HttpServletResponse response) {
// Your logic for extracting credentials goes here
return null; // Return AuthenticationInfo (AuthenticationInfo authInfo) if credentials are extracted
}
@Override
public boolean requestCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException {
// Logic for requesting credentials if needed
return false;
}
@Override
public void dropCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException {
// Logic for dropping credentials on logout
}
@Override
public boolean authenticationSucceeded(HttpServletRequest request, HttpServletResponse response,
AuthenticationInfo authInfo) {
// Logic for successful authentication
return false; // Return true if the request should continue, false otherwise
}
@Override
public void authenticationFailed(HttpServletRequest request, HttpServletResponse response,
AuthenticationInfo authInfo) {
// Logic for handling failed authentication
}
@Override
public void dropCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException {
TokenCookie.update(request, response, this.repositoryId, null, null,
true);
/*Dropping the email Remember Me cookie at the Logout click. */
LoginAuthenticationHandler.setCookie(response, USER_COOKIE, "0", 0, "/", null, true, request.isSecure());
/*Dropping the eCards cookie at the Logout click. */
LoginAuthenticationHandler.setCookie(response, RENEW_LATER_COOKIE, "0", 0, "/", null, true, request.isSecure());
/*Dropping the Impersonation cookie at the Logout click. */
LoginAuthenticationHandler.setCookie(response, IMPERSONATION_USER_COOKIE, "0", 0, "/", null, true, request.isSecure());
}
// Additional helper methods can be defined here
}
Explanation of Key Components
extractCredentials: This method is responsible for extracting user credentials from incoming requests. You can implement custom logic to parse request parameters, headers, or cookies to retrieve authentication information.
requestCredentials: If authentication is required but not provided, this method initiates the process of requesting credentials from the user. It can trigger authentication challenges such as login forms or redirects to authentication pages.
authenticationSucceeded: Handles successful authentication attempts. You can implement logic for setting up user sessions, generating authentication tokens, or redirecting users to authorized resources.
authenticationFailed: Deals with failed authentication scenarios. You can customize error handling, logging, or redirection behavior based on your application requirements.
dropCredentials: Invoked when a user logs out or authentication credentials need to be invalidated. It performs cleanup tasks such as removing session tokens or clearing user cookies.
Conclusion
Implementing a custom authentication handler in AEM provides flexibility and control over user authentication processes. Whether you need to integrate with external identity providers, enforce custom authentication policies, or enhance user authentication workflows, custom authentication handlers empower you to tailor authentication mechanisms to your specific use cases.
By following the steps outlined in this guide and leveraging the capabilities of the Sling authentication framework, you can implement robust and secure authentication solutions tailored to your AEM application
Stay tuned for more AEM insights and tips. Happy coding!