Protecting adapter procedures for backend access

Overview

MobileFirst Platform Foundation Server allows to protect adapter procedures via several types of authentication, using security tests, realms and login modules.

The MobileFirst client-side SDK helps you handle challenges sent by the server when trying to access a protected resource.

One question that often surfaces is: What if my backend needs to invoke an adapter procedure? A common example of this scenario is Push notifications. You want your backend to send push notifications via the MobileFirst server. To do so, your backend needs to invoke an adapter procedure.

MobileFirst adapters can be invoked via simple HTTP requests. The URL for the invocation is service is http(s)://{server}:{port}/{Context}/invoke. The request needs to include the parameters: adapter, procedure, parameters.

You would not want anyone who knows this URL to be able to send push notifications to your users. Therefore you need to protect the adapter procedure with a security test that your backend can easily handle.

One possible solution for this: HTTP's Basic Access Authentication. Basic access authentication sends a username and password in the HTTP headers, encoded in base64.

Authenticator & Realm

MobileFirst includes an Authenticator able to extract and decode this header. To use it, add a new realm in your authenticationConfig.xml which uses the class com.worklight.core.auth.ext.BasicAuthenticator:

1
2
3
4
<realm name="BackendAccessRealm" loginModule="BackendAccessLogin">
  <className>com.worklight.core.auth.ext.BasicAuthenticator</className>
  <parameter name="basic-realm-name" value="Private"/>
</realm>
  • name: An arbitrary name for the realm.
  • loginModule: The login module that will verify the credentials. Defined later.
  • className: The class provided out of the box by MobileFirst to extract the authentication headers.
  • basic-realm-name: An arbitrary name for the HTTP realm.

If you prefer to use your own custom HTTP header or your own credentials encoding, you can write a custom authenticator instead of the provided class.

Login Module

To validate credentials, a Login Module is used. In our case there is only one set of credentials that are are valid. Let's add the valid credentials in the worklight.properties.

1
2
3
4
5
##
# Backend access credentials
##
backend.username=user
backend.password=password

Define a login module to use those properties:

1
2
3
4
5
<loginModule name="BackendAccessLogin">
  <className>com.sample.auth.ConfiguredIdentityLoginModule</className>
  <parameter name="username-property" value="backend.username"/>
  <parameter name="password-property" value="backend.password"/>
</loginModule>
  • name: an arbitrary name, to be used in the realm.
  • className: The custom Java class responsible for checking the credentials. Code will be covered below. It needs to be added to your java folder.
  • username-property: the name of the username property in worklight.properties
  • password-property: the name of the password property in worklight.properties

Our ConfiguredIdentityLoginModule class is responsible for comparing the credentials collected by authenticator to the credentials stored in the properties file. If you prefer to store/retrieve the credentials some other way, you can modify this code.

To see the full code for this class: https://ibm.biz/BdETLK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public void init(Map<String, String> options) throws MissingConfigurationOptionException {
  String usernameProperty = options.remove(USERNAME_PROPERTY_CONF);
  if (usernameProperty == null) throw new MissingConfigurationOptionException(USERNAME_PROPERTY_CONF);
  String passwordProperty = options.remove(PASSWORD_PROPERTY_CONF);
  if (passwordProperty == null) throw new MissingConfigurationOptionException(PASSWORD_PROPERTY_CONF);
  super.init(options);
  WorklightConfiguration conf = WorklightConfiguration.getInstance();
  configuredUsername = conf.getStringProperty(usernameProperty);
  configuredPassword = conf.getStringProperty(passwordProperty);
  if (configuredUsername == null || configuredUsername.length() == 0) {
    throw new IllegalStateException("ConfiguredIdentityLoginModule cannot resolve property " + usernameProperty + ". Please check project configuration properties.");
  }
  if (configuredPassword == null || configuredPassword.length() == 0) {
    throw new IllegalStateException("ConfiguredIdentityLoginModule cannot resolve property " + usernameProperty + ". Please check project configuration properties.");
  }
}
@Override
public boolean login(Map<String, Object> authenticationData) {
  populateCache(authenticationData);
  return configuredUsername.equals(username) &amp;&amp; configuredPassword.equals(password);
}

Protecting the procedure

Combine all of the above in a custom security that uses the defined realm:

1
2
3
<customSecurityTest name="BackendAccessSecurity">
 <test realm="BackendAccessRealm" isInternalUserID="true"/>
</customSecurityTest>

Protect your procedure with the newly created security test:

1
<procedure name="doAction" securityTest="BackendAccessSecurity"/>

Adapter JS:

1
2
3
4
5
6
function doAction(data){
	//Here do something with the data received by the backend
	//A classic example is Push Notifications
    WL.Logger.info("doAction was called with data = " + data);
	//Don't forget to set the correct console log level in the server properties
}

Testing

Your backend can now make HTTP requests to the invocation service using your favorite HTTP client. Pass the base64 authentication credentials in the headers: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

For testing purposes, you can use tools such as PostMan. It includes a feature to generate the base64 credentials for testing Basic Auth.

missing_alt

Keep in mind that your browser will keep the JSESSIONID cookie and use it for subsequent requests. You will need to clear the browser cookies to start a new scenario.

Sample Code

The full sample used here is available on GitHub.

Last modified on October 27, 2016
Share this post: