Implementing Java-based custom authenticators

You can use default MobileFirst login modules and authenticators, or customize your own.

About this task

You can write custom login modules and authenticators when those that IBM MobileFirst™ Platform Foundation supplies do not match your requirements.

Procedure

  1. Configure the authenticationConfig.xml file.

    For more information, see The authentication configuration file.

  2. Code the server side.

    You create custom login modules and authenticators as instances of Java™ classes, which you must place in the server/java folder of the project. They are server-side entities and they are packed inside the WAR file of the project. The authenticator, login module, and user identity instances are stored in a session scope, so that they exist while the session is active.

    Important: To correctly support session-independent mode, observe the following guidelines. For a detailed overview of session-independent mode, refer to Session-independent mode.
    • Ensure that you do not save any applicative data in the HTTP session.
    • In your custom authenticator implementation, do not send ("flush") the prepared server-authentication response to the client. Sending the response while running in session-independent mode might lead to incorrect behavior if the client receives the response before the authentication context is saved to the attribute store. If the authenticator does not send the response to the client, MobileFirst Server does so automatically after all authentication-context information is saved to the attribute store.
    Authenticator interface and methods
    Your custom authenticator class must implement the com.worklight.server.auth.api.WorkLightAuthenticator interface. The WorkLightAuthenticator interface extends the java.io.Serializable interface, so as a result, your custom authenticator implements java.io.Serializable. This means that your custom authenticator must have no non-serializable members and also declare a serialVersionUID version number, as recommended in the Serializable Interface Java documentation.
    The custom authenticator must implement the following methods:
    • init: This method is called when the authenticator instance is created. It receives the options that are specified in the realm definition in the authenticationConfig.xml file.
    • processRequest: This method is called for each request from an unauthenticated session. The method must return an AuthenticationResult status. While the request is processed, the method might retrieve data from the request and write data to the response.
      The AuthenticationResult status can return the following values:
      • SUCCESS: The credentials were successfully collected and the login module can now validate them.
      • CLIENT_INTERACTION_REQUIRED: The client must still supply authentication data.
      • REQUEST_NOT_RECOGNIZED: The authenticator is not handled.
    • processAuthenticationFailure: This method is called if the login module returns a failure for the validation of credentials.
    • processRequestAlreadyAuthenticated: This method is called for each request from a session that has already been authenticated. It returns an AuthenticationResult value for authenticated requests.
    • getAuthenticationData: Login modules use this method to retrieve the credentials that are collected by an authenticator.
    • changeResponseOnSuccess: This method is called after the login module successfully validates credentials. Use this method to notify a client application of the success of the authentication, for example to modify the response before it is returned to the client. This method must return true if the response was modified orfalse otherwise.
    • clone: This method creates a deep copy of the object members.
    Login module interface and methods
    Your custom login module class must implement the com.worklight.server.auth.api.WorkLightAuthLoginModule interface. The WorkLightAuthLoginModule interface extends the java.io.Serializable interface, so as a result, your custom login module implements java.io.Serializable. This means that your login module must have no non-serializable fields and also declare a serialVersionUID version number, as recommended in the Serializable Interface Java documentation.
    The login module must implement the following methods:
    • init: This method is called when the login module instance is created. This method receives the options that are specified in the login module definition of the authenticationConfig.xml file.
    • login: This method is called after the authenticator returns SUCCESS status. It receives an authenticationData object from the authenticator and validates the credentials that are collected by the authenticator. If the credentials are valid, the method returns true. If the credential validation fails, the method returns false or raises a runtime exception. In this case, the exception string that is returned to the authenticator as an errorMessage parameter.
    • createIdentity: This method is called after the credentials are successfully validated. The method creates and returns a UserIdentity object, which contains information about the authenticated user, such as unique user name, display name, Java security roles, and custom user attributes.
    • logout: Use this method to clean up cached data and class members after the user logs out.
    • abort: Use this method to clean up cached data and class members after the user stops the authentication flow.
    • clone: This method creates a deep copy of the object members.
  3. Code the client side.

    You must declare a challenge handler in the application to handle challenges from the custom authenticator realm. The following sample shows one way to implement a challenge handler class:

    var myChallengeHandler = WL.Client.createChallengeHandler("CustomAuthenticatorRealm");
    The challenge handler must implement the following methods:
    • isCustomResponse: This method is called each time that a response is received from the server. It detects whether the response contains data that is related to this challenge handler. It must return true or false. Here is a simple example:
      sampleAppRealmChallengeHandler.isCustomResponse = method(response) {
        return false;
      };
    • handleChallenge: Use this method for such actions as hide application screen and show login screen. If the isCustomResponse method returns true, the handleChallenge method is called by the framework. Here is a simple implementation, as an example:
      sampleAppRealmChallengeHandler.handleChallenge = method(response) {
      };
    Optionally, the challenge handler can also implement the following methods:
    • submitLoginForm: This method sends the collected credentials to a specific URL. You can also specify request parameters, headers, and callback.
    • submitSuccess: This method notifies the MobileFirst framework that the authentication finished successfully. The MobileFirst framework then automatically issues the original request that triggered the authentication.
    • submitFailure: This method notifies the MobileFirst framework that the authentication process failed. The MobileFirst framework then disposes of the original request that triggered the authentication.

Example

The following example shows how to implement a custom authenticator and login module. In the example, an adapter procedure is protected by a custom authenticator. When the user attempts to call the procedure from the application, the application requests the user's credentials and the authentication process starts.

Configuration of the authenticationConfig.xml file
<securityTests>
  <customSecurityTest name="DummyAdapter-securityTest">
      <test isInternalUserID="true" realm="CustomAuthenticatorRealm"/>
   </customSecurityTest>
</securityTests>

<realms>
  <realm name="CustomAuthenticatorRealm" loginModule="CustomLoginModule">
      <className>com.mypackage.MyCustomAuthenticator</className>
  </realm>
</realms>

<loginModules>
  <loginModule name="CustomLoginModule">
      <className>com.mypackage.MyCustomLoginModule</className>
  </loginModule>
</loginModules>
Coding the server side
Code the following elements on the server side: adapter, authenticator, and login module.
  • Adapter:
    1. Create a MobileFirst adapter.
    2. Add a procedure and protect it with the custom security test that you created earlier. The implementation can return some hardcoded value. For example:
      <procedure name="getSecretData" securityTest="DummyAdapter-securityTest"/>
  • Authenticator:
    1. Create a MyCustomAuthenticator.java class in the server/java/com/mypackage folder. This class must implement the com.worklight.server.auth.api.WorkLightAuthenticator interface, as follows:
      public class MyCustomAuthenticator implements WorkLightAuthenticator{}
    2. Add the generated serialVersionUID constant to the class.
    3. Implement the mandatory methods of the class.
      • processRequest: This method retrieves the user name and password credentials that are passed as request parameters. Check the credentials for basic validity, collect the credentials, and return SUCCESS. If a problem occurs with the received credentials, add an errorMessage to the response and return the CLIENT_INTERACTION_REQUIRED status message. If the request does not contain authentication data, add the authStatus:required property to the response and again, return a CLIENT_INTERACTION_REQUIRED status message.
        public AuthenticationResult processRequest(HttpServletRequest request, HttpServletResponse response, boolean isAccessToProtectedResource) throws IOException,   ServletException {
            if (request.getRequestURI().contains("my_custom_auth_request_url")){
                String username = request.getParameter("username");
                String password = request.getParameter("password");
                 
                if (null != username && null != password && username.length() > 0 && password.length() > 0){
                    this.username = request.getParameter("username");
                this.password = request.getParameter("password");
                    return AuthenticationResult.createFrom(AuthenticationStatus.SUCCESS);
                } else {
                    response.setContentType("application/json; charset=UTF-8");
                    response.setHeader("Cache-Control", "no-cache, must-revalidate");
                    response.getWriter().print("{\"authStatus\":\"required\", \"errorMessage\":\"Please enter username and password\"}");
                    return AuthenticationResult.createFrom(AuthenticationStatus.CLIENT_INTERACTION_REQUIRED);
                }
            }
             
            if (!isAccessToProtectedResource)
                return AuthenticationResult.createFrom(AuthenticationStatus.REQUEST_NOT_RECOGNIZED);
             
            response.setContentType("application/json; charset=UTF-8");
            response.setHeader("Cache-Control", "no-cache, must-revalidate");
            response.getWriter().print("{\"authStatus\":\"required\"}");
            return AuthenticationResult.createFrom(AuthenticationStatus.CLIENT_INTERACTION_REQUIRED);
        }
      • processAuthenticationFailure: This method writes an error message to a response body and returns the CLIENT_INTERACTION_REQUIRED status message.
        public AuthenticationResult processAuthenticationFailure(HttpServletRequest
           request, HttpServletResponse response, String errorMessage) throws IOException, ServletException {
          response.setContentType("application/json; charset=UTF-8");
          response.setHeader("Cache-Control", "no-cache, must-revalidate");
          response.getWriter().print("{\"authStatus\":\"required\", \"errorMessage\":\"" + errorMessage + "\"}");
          return AuthenticationResult.createFrom(AuthenticationStatus.CLIENT_INTERACTION_REQUIRED);
        }
  • Login module:
    1. Create a MyCustomLoginModule.java class in the server/java/com/mypackage folder. This class must implement the com.worklight.server.auth.api.WorkLightAuthLoginModule interface.
      public class MyCustomLoginModule implements WorkLightAuthLoginModule{}
    2. Implement the mandatory methods of the class.
      • login: This method retrieves the user name and password credentials that the authenticator stored previously. In this example, the login module validates the credentials against hardcoded values. You can implement your own validation rules. If the credentials are valid, the login method returns true. For example:
        public boolean login(Map<String> authenticationData) {
          USERNAME =(String) authenticationData.get("username");
          PASSWORD = (String) authenticationData.get("password");
          if (USERNAME.equals("wluser") && PASSWORD.equals("12345"))
          return true;
          else   throw new RuntimeException("Invalid credentials"); }
        </String>
      • createIdentity: This method is called when the login method returns true. It is used to create a UserIdentity object. In that object, you can store your own custom attributes and use them later in Java or adapter code. The UserIdentity object contains user information. Its constructor is as follows:
        public UserIdentity(String loginModule, String name, String displayName, Set<String> roles, Map<String, Object> attributes, Object credentials)
        Here is an example of how to implement this method:
        public UserIdentity createIdentity(String loginModule) {
          HashMap<String, Object> customAttributes = new HashMap<String, Object>();
          customAttributes.put("AuthenticationDate", new Date());
        
          UserIdentity identity = new UserIdentity(loginModule, USERNAME, null, null, customAttributes, PASSWORD);
          return identity;
        }
Coding the client side
Follow these steps:
  1. Create a MobileFirst application.
  2. Create a challenge handler in the application to handle challenges from the custom authenticator realm. For example:
    var myAppRealmChallengeHandler = 
    WL.Client.createChallengeHandler ("CustomAuthenticatorRealm");
  3. Implement the mandatory isCustomResponse and isCustomResponse methods, and optional methods of the challenge handler, as described in Step 3.

What to do next

For a more extensive example of implementing custom authentication and login, see the Custom authentication in hybrid applications tutorial.

IBM MobileFirst Platform Foundation provides other custom authentication mechanisms: