DEV Community

Saravanan Muniraj
Saravanan Muniraj

Posted on

Building Custom AutoLogin Module for Liferay Integration with Azure AD B2C

Azure AD B2C provides a robust, cloud-based identity management solution that offers secure authentication, multi-factor authentication support, and seamless integration with enterprise systems. When combined with Liferay's flexible portal capabilities, you get a powerful platform that can handle complex authentication scenarios while maintaining a great user experience.

The Solution: Custom AutoLogin Module

The approach involves creating a custom AutoLogin component that intercepts the authentication flow, validates tokens from Azure B2C, and automatically logs users into Liferay. Let's break down the implementation.

Step 1: Create the AutoLogin Class

First, we need to create a class that extends BaseAutoLogin. This class will handle the authentication logic:

public class WebsiteAutoLogin extends BaseAutoLogin {

    @Reference
    private UserLocalService userLocalService;

    private RestTemplate restTemplate;

    @Activate
    public void activateComponent() {
        restTemplate = new RestTemplate();
    }
}
Enter fullscreen mode Exit fullscreen mode

The @Reference annotation injects Liferay's UserLocalService, which we'll use to look up users. The RestTemplate is initialized during component activation and will be used for making HTTP calls to Azure B2C.

Step 2: Implement the doLogin Method

The core authentication logic resides in the doLogin method:

public String[] doLogin(HttpServletRequest request, HttpServletResponse response)throws AutoLoginException {

    // 1) Extract the ID token from the request
    String code = ParamUtil.getString(request, "id_token");

    // 2) Exchange code for ID token - Call the B2C REST API
    String tokenEncodedString = retrieveAzureToken(code);

    // 3) Decode the JWT payload
    JSONObject payload = decodeJWTToken(tokenEncodedString);

    // 4) Extract user identifier from the token
    String screenName = payload.getString("MatchingScreenName");

    try {
        long companyId = PortalUtil.getCompanyId(request);
        // 5) Fetch existing user by screen name
        User user = userLocalService.fetchUserByScreenName(companyId, screenName);

        // 6) Return credentials for auto-login
        return new String[] {
            String.valueOf(user.getUserId()),
            user.getPassword(),
            Boolean.TRUE.toString()

        };

    } catch (Exception e) {
        logger.error("Error during user login or creation: " + e.getMessage(), e);
        return null;
    }

}
Enter fullscreen mode Exit fullscreen mode

This method performs the following steps:

Extracts the authorization code or ID token from the incoming request
Exchanges this code with Azure B2C to obtain a valid token
Decodes the JWT token to extract user information
Looks up the corresponding Liferay user
Returns the authentication credentials for automatic login
Step 3: Token Retrieval from Azure B2C

The retrieveAzureToken method handles communication with Azure B2C's token endpoint:

private String retrieveAzureToken(String authorizationCode) {
    try {
        // Get the token endpoint URL
        String tokenEndpoint = getTokenEndpoint();
        // Create HTTP headers
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        // Encode the redirect URI
        String redirectUri = URLEncoder.encode("YOUR_REDIRECT_URL", "UTF-8");
        // Build form parameters
       MultiValueMap<String, String> formParams = new LinkedMultiValueMap<>();
        formParams.add("client_id", B2C_AZURE_CLIENT_ID);
        formParams.add("client_secret", B2C_AZURE_CLIENT_SECRET);
        formParams.add("code", authorizationCode);
        formParams.add("grant_type", AUTH_GRANT_TYPE);
        formParams.add("redirect_uri", redirectUri);
        // Create and send the request
        HttpEntity<MultiValueMap<String, String>> requestEntity =
            new HttpEntity<>(formParams, headers);
        ResponseEntity<String> response = restTemplate.postForEntity(
            tokenEndpoint,
            requestEntity,
            String.class

        );
        // Parse and return the ID token
        JSONObject jsonObject = JSONFactoryUtil.createJSONObject(response.getBody());
        return jsonObject.getString("id_token");
    } catch (Exception e) {
        logger.error("Exception occurred on B2C Azure Token Request: " + e);
    }

    return null;
}
Enter fullscreen mode Exit fullscreen mode

Step 4: JWT Token Decoding

The JWT token returned by Azure B2C needs to be decoded to extract user claims:

public static JSONObject decodeJWTToken(String token) {
    try {
        if (Validator.isNull(token)) return null;
        // Split the token into header, payload, and signature
        String[] parts = token.split("\\.");
        // Decode the payload (second part) from Base64
        return JSONFactoryUtil.createJSONObject(decode(parts[1]));
    } catch (Exception e) {

        logger.error("Exception occurred while decoding the JSON token: " + e.getMessage());
    }
    return null;
}
Enter fullscreen mode Exit fullscreen mode

Configuration Requirements

Before deploying this module, ensure you have the following configured:

Azure AD B2C Configuration:

Register your application in Azure AD B2C
Configure the redirect URIs
Note your Client ID and Client Secret
Set up the appropriate user flows (sign-in, sign-up)
Liferay Configuration:

Deploy the custom AutoLogin module
Configure the module with your Azure B2C credentials
Ensure users exist in Liferay with matching screen names
Security Considerations

When implementing this solution, keep these security best practices in mind:

Secure credential storage: Store your Azure B2C client secrets in Liferay's secure configuration, not in code
Token validation: Always validate the JWT token's signature and expiration before trusting its claims
HTTPS only: Ensure all communication happens over HTTPS
Error handling: Implement proper error handling to avoid exposing sensitive information
Logging: Log authentication events for auditing, but never log sensitive token data

Conclusion

Integrating Azure AD B2C with Liferay through a custom AutoLogin module provides a secure and seamless authentication experience. This approach leverages Azure's enterprise-grade identity management while maintaining Liferay's flexibility and extensibility.

The complete implementation gives you a foundation that can be extended to support additional features like user provisioning, role mapping based on Azure AD groups, and multi-factor authentication.

Top comments (0)