+

Search Tips   |   Advanced Search

Implementing custom authenticators

We can use default MobileFirst login modules and authenticators, or customize our own.

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

  1. Configure authenticationConfig.xml.

    See The authentication configuration file.

  2. Code the server side.

    We create custom login modules and authenticators as instances of Java classes, which 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 they exist while the session is active.

    Authenticator interface and methods

    Your custom authenticator class must implement the com.worklight.server.auth.api.WorkLightAuthenticator interface. The custom authenticator must implement the following methods:

    • init: This method is called when the authenticator instance is created. It receives the options specified in the realm definition in authenticationConfig.xml.

    • 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 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 login module must implement the following methods:

    • init: This method is called when the login module instance is created. This method receives the options specified in the login module definition of authenticationConfig.xml.

    • login: This method is called after the authenticator returns SUCCESS status. It receives an authenticationData object from the authenticator and validates the credentials 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.

    We 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, thehandleChallenge 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. We 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 authenticationConfig.xml./dt>

<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 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. 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, create an authenticationData object, 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){
              authenticationData = new HashMap<String, Object>();
              authenticationData.put("username", username);
              authenticationData.put("password", 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. We can implement our 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, we can store our 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 module Custom Authenticator and Login Module in hybrid applications in Tutorials, samples, and additional resources.


Parent topic: Configure authenticators and realms