IBM Support

Configuring Maximo Anywhere with an Authenticating Proxy Server

Technical Blog Post


Abstract

Configuring Maximo Anywhere with an Authenticating Proxy Server

Body

Are you trying to deploy Maximo Anywhere into an environment with an authenticating proxy server (like IBM Security Access Manager or Datapower) between your mobile apps and your Intranet?  You may not be able to login to your Maximo Anywhere apps without performing additional configuration steps.

image

 

The default login code of our Maximo Anywhere app assumes that a direct connection can be made to the MobileFirst server, and expects the login challenge to be formatted in a certain way. If you have an authenticating proxy between your device and the MobileFirst server, the initial request from the device to get data from the MobileFirst adapter will be blocked by this proxy. The proxy server's challenge will be returned to the app instead of the MobileFirst server's authentication challenge. There are few standards for challenging for this authentication and responding to this challenge, so you need to make changes to your Maximo Anywhere application source code to detect and respond to your specific proxy server's challenge. The next figure shows what this authentication flow looks like when a proxy server is between the device and the MobileFirst server.

image

 

 

Here's an example of how to configure a standard MobileFirst hybrid application to work with Datapower. For Maximo Anywhere you won't follow these exact instructions, but the basic concepts are the same. Read these documents to understand the general customizations you will need to make.

https://www.ibm.com/support/knowledgecenter/SSHS8R_7.1.0/com.ibm.worklight.installconfig.doc/admin/t_protecting_your_mobile_app_traffic_using_datapower.html?lang=en

https://mobilefirstplatform.ibmcloud.com/blog/2016/02/20/datapower-integration/

For Maximo Anywhere, we make things a little simpler for you. First, we already provide a CustomChallengeHandler class in our platform under the common/js/platform/auth directory of your application.
Because this is a platform file this might be hidden from your Eclipse view by default. You can open it outside of Eclipse.

You then override two existing methods of this class: isCustomResponse: to implement your own "custom login form detection", and login to respond to the proxy server and pass the credentials in a way that it understands. This "form detection" and "form response" logic is specific to your proxy server configuration. Contact your proxy server administrator for details of how your proxy server challenges for credentials and the response format it expects to receive. Your proxy server should also be configured to return a 401 response code if a request comes in without credentials, or without cookie/header information. Here's a sample implementation of a isCustomResponse method that detects a proxy server's basic authentication prompts for credentials. The isCustomResponse method should return true if the proxy server is prompting for credentials, and false if the proxy server is allowing the traffic through.

    /*
         * evaluates the response from server, if authStatus is returned,
         * then, we determine if this is a response from CustomAuthenticatorRealm.
         */
        isCustomResponse: function(response) {
            //Need to skip init and login in case they're blocked by the proxy, they will be resent automatically by worklight
            if (response.request.url.indexOf('init') > 0 || response.request.url.indexOf('login') > 0 || response.request.url.indexOf('loguploader') > 0) {
                return false;
            }
            //Need to check for direct 401 bounce on the query request to handle the initial rejection by the MobileFirst proxy
            if((response.request.url.indexOf('query') > 0 && response.status=='401')|| (response.responseJSON && response.responseJSON.authStatus) || (UserAuthenticationManager._isServerLoginFailure(response) && !UserAuthenticationManager.isCachingUserInfo())){
                Logger.trace('[CustomChallangeHandler.isCustomResponse] true - authStatus');
                return true;
            }
            else {
                Logger.trace('[CustomChallangeHandler.isCustomResponse] false');
                return false;
            }
        },

 

 

Here's a sample implementation of a login method that responds to this proxy server with basic authentication credentials. If you have written your isCustomResponse method correctly, your CustomChallengeHandler's login method will be called once the end user clicks the Login button on our Maximo Anywhere Application login page. The login method is passed the user and pwd that were typed into the Maximo Anywhere app login form. The login method is also passed a deferred object. If your proxy accepts the login credentials, you must call deferred.resolve(result). If your proxy rejects the login credentials, you must call and deferred.reject(result).

/*
         * Send Login credentials to your authentication service, you are passed a user and password from the login form
         *
         * ATTN: You do not return a response to this method, you resolve or reject the passed in deferred thread
         * based on the asynchronous response from the server.
         *
         * If you override this method, don't forget to indicate that you're in the middle of authentication
         * by setting and checking the isAuthenticating boolean to handle possible multiple threads.
         *
         */
        login: function(user, pwd, deferred) {
            
            this.isAuthenticating = true;
            //Assuming I need to pass Basic Authorization headers to be let through..
            WL.Client.addGlobalHeader("Authorization", "Basic " + btoa(user+":"+pwd));

            var self = this;
            var sysPropDeferred = SystemProperties.loadPlatformProperties();
            sysPropDeferred.always(function(){
                self.getLoginOptions(user, pwd)
                .then(lang.hitch(self, function(loginOptions) {
                    Logger.trace('CustomChallengeHandler login calling submitLoginForm');
                    
                    //For some reason I have to explicitly pass the Basic Authorization headers to the submitLogin Form, it doesn't pickup from the GlobalHeader..
                    loginOptions.headers = {"Authorization": "Basic " + btoa(user + ":" +pwd)};
                    var resubmit = true;
                    var afterSubmit = function(response) {
                        Logger.trace('CustomChallengeHandler.sendLogin');
                        if(self._isAuthenticated(response)) {
                            Logger.trace('[CustomChallengeHandler] User successfully authentication against realm "' + self.REALM + '"');                                
                            deferred.resolve(response);
                            self.isAuthenticating = false;
                        }
                        else if (resubmit && (response['responseJSON'] && response['responseJSON']['challenges'] && response['responseJSON']['challenges']['wl_antiXSRFRealm']) || response.status == '500'){
                                //This is the XSRF error.  So call the client login to get the token needed to get past this
                                //The submit the login again
                                WL.Client.login(CUSTOM_AUTH_REALM_NAME);
                                resubmit = false;
                                self.submitLoginForm(self.getAuthURL(), loginOptions, lang.hitch(self,afterSubmit));
                        }
                        else{
                            var responseError = self._parseAuthenticationError(response);
                            deferred.reject(responseError);
                            self.isAuthenticating = false;
                        }
                    };
                    self.submitLoginForm(self.getAuthURL(), loginOptions, lang.hitch(self,afterSubmit));
            }));
            });
        },

 

Notice this code adds a global authorization header for our proxy server. This should allow our requests to make it through the proxy server to the backend MobileFirst server. We then call self.submitLoginForm to send our credentials to the proxy server to be validated. Your proxy server should be configured to pass these credentials through to the backend MobileFirst server as well so that our standard TpaeCustomAuthenticator and TpaeLoginModule can continue to be used without requiring additional configuration on the MobileFirst server.

After making these changes and rebuilding and redeploying the apps, you should now be allowed to login from your apps. The credentials should be sent through your proxy server, into the backend MobileFirst server, and then these credentials should be passed to the Maximo server for validation with the Maximo server.

If you want to support downloading an installing your apps through MobileFirst Application Center, you also may need to configure your MobileFirst Application Center behind the Reverse Proxy by following these instructions:

https://www.ibm.com/support/knowledgecenter/SSHS8R_7.1.0/com.ibm.worklight.installconfig.doc/install_config/c_reverse_proxy_topols.html?lang=en

[{"Business Unit":{"code":"BU005","label":"IoT"}, "Product":{"code":"SSLKT6","label":"Maximo Asset Management"},"Component":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"","Edition":""}]

UID

ibm11131225