+

Search Tips   |   Advanced Search

Use SSO between MPF and external services

We can use single sign-on (SSO) between MPF and external services using the MobileFirst security framework to protect the external services.

MobileFirst Server acts as an authorization server and issues an access token that can be validated by the external service. The client application requests the access token from IBM MobileFirst Platform Foundation via the token endpoint and sends it to the external services.

The scope of the access token is a security test defined inside a MobileFirst project. Each scope has a timeout property that determines the lifetime of the token. This property defines the time for which an issued token remains valid. After the timeout expires, the token is rejected and a new one needs to be requested from the server.

Restriction: If MobileFirst Server and the external service are visible to the client through different domains, the following restrictions apply:

  1. Configure the project.

    1. Configure the scope for the access token.

      The scope of an access token must be a predefined security test in a MobileFirst project. The security test is configured in your_project/server/conf/authenticationConfig.xml. The default lifetime for each token is 60 seconds, which we can override by adding the AccessTokenExpirationSec attribute to the security test. For example, to configure a security test called SampleSecurityTest with a lifetime of 15 seconds, you edit authenticationConfig.xml.in either of the following ways:

      From Source view:

      <securityTests>
          <customSecurityTest name="SampleSecurityTest" AccessTokenExpirationSec="15">
              <test realm="SampleRealm" isInternalUserID="true"/>
          </customSecurityTest>
      </securityTests>

      From Design view:

    2. Use a keystore.

      Create and use our own keystore, and configure the MobileFirst Server to use it. For information about how to create a keystore in an unrelated context, see Configure device auto provisioning.

      Attention: Use the default keystore is not secure.

  2. Configure the external service.

    To ensure that the external service accepts the access token, we must add a validation library to the service, such that that library can validate the token either online or offline. Two libraries are provided for this purpose:

    • Java lib: worklight-access-token-validator.jar

    • Node.js module: worklight-access-token-validator.tgz

    We can find the libraries in the following directories:

    For MobileFirst Server installation

    In MF_HOME/WorklightServer/external-server-libraries.

    For MobileFirst Studio

    When creating a new project, in: your_project_dir/externalServerLibraries.

    We must use one of the following options:

    • Option i: Configure the external service using Java.

      The purpose of this module is to allow offline validation of access tokens generated by MobileFirst Server for Java web applications.

      To use the Java library, two files are needed:

      • Certificate of MobileFirst Server.

        Export the certificate from the keystore of the MobileFirst Server. We can do this with the Java keytool.

      • worklight-access-token-validator.jar.

      We can use either a servlet filter, or use the Java-supplied API:

      Use a servlet filter

      Add this JAR to the class path of the web application, and use the filter class com.worklight.security.WLAccessTokenValidationFilter as shown in the following example. Assume the values in the following table:

      Parameter Value Explanation
      Filter name FilterName Choose an arbitrary name for the filter.
      URL /some/protected/url Prefix for all the resources to protect.
      Scope securityTestName Optional. Name of the security test, as defined in authenticationConfig.xml. which is needed to authenticate against in order to gain access to the protected resources.
      CertificatePath certificateLib/WorklightServerCertificate.cert Path to the certificate of the MobileFirst Server relative to the WEB-INF folder.

      Assuming the parameter values in the previous table, this is the addition needed for the web.xml of your external server:

      <web-app …>
      …
      <filter>
          <filter-name>FilterName</filter-name>
          <filter-class>com.worklight.security.WLAccessTokenValidationFilter</filter-class>
          <init-param>
              <param-name>worklightCertificateFile</param-name>
              <param-value>certificateLib/WorklightServerCertificate.cert</param-value>
          </init-param>
          <init-param>
              <param-name>scope</param-name>
              <param-value>securityTestName</param-value>
          </init-param>
      </filter>
      <filter-mapping>
          <filter-name>FilterName</filter-name>
          <url-pattern>/some/protected/url</url-pattern>
      </filter-mapping>
      … 
      </web-app>

      After successful validation, the filter updates the ClientContext object that can be used by the service to access user, application, or device identities contained in the access token. This is an example of ClientContext usage:

      ClientContext context = ClientContext.getInstance();
      String appId = context.getApplcation();
      String userId = context.getUser();
      String deviceId = context.getDevice();

      Use the Java-supplied API

      The following interface is exposed:

      package com.worklight.common.security.oauth;
      public interface IAccessToken {
        public String getUserIdentity();
        public String getDeviceIdentity();
        public String getApplicationIdentity();
        public String getVersion();
        public String getScope();
      }

      The class AccessTokenParse provides the following API in order to get an instance that implements this interface:

      public static IAccessToken parseToken(final String tokenStr, final PublicKey serverPublicKey, String scope) throws AccessTokenException;
      public static IAccessToken parseToken(final String tokenStr, final PublicKey serverPublicKey) throws AccessTokenException;

      Both methods check the validity of the token (correctly formatted token and issued by MobileFirst Server for the given public key), and that the token has not expired.

      The only difference between the two methods is that the first method also validates that the token was issued for the given scope. The public key needs to be taken from the certificate: For example:

      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      X509Certificate cert = (X509Certificate) cf.generateCertificate(certInputStream);
      wlPublicKey = cert.getPublicKey();

      The interface can be used in the following way:

      try {
        IAccessToken iAccessToken = null;
        if (scope != null) {
          iAccessToken = AccessTokenParser.parseToken(wlToken, wlPublicKey, scope);
        } else {
          iAccessToken = AccessTokenParser.parseToken(wlToken, wlPublicKey);
        String userId = iAccessToken.getUserIdentity();
        String deviceId = iAccessToken.getDeviceIdentity();
        String appId = iAccessToken.getApplicationIdentity();
      …
      …

    • Option ii: Configure the external service using Node.js.

      The purpose of this node module is to allow offline validation of access tokens generated by MobileFirst Server for Node.js server.

      1. Certificate of MobileFirst Server.

        To get the certificate, we need to generate a .pem certificate from the keystore. One possible way to do so is with Java's keytool. For example, from bash, creating the .pem certificate from jks keystore:

          keytool -exportcert -keystore $KEYSTORE_FILE -alias $CERTIFICATE_ALIAS -rfc > $OUTPUT_FILE.pem

        You then have to pass the content of the generated (PEM-formatted) certificate file as input to the node module, which allows you to validate tokens created with the same certificate.

        The expiration of the token is checked against the local computer time, so ensure the clock is synchronized (preferably using an NTP server).

      2. worklight-access-token-validator.tgz

        You will need to install the module with:

          npm install tgz file

        Using this module gives you a function that requires certificate (mandatory) and a scope (optional). Once called with these parameters, we have an object with the following functions:

        Function Description
        validate(token, callback)

        A function that validates if the token provided is a valid MobileFirst access token. If the scope parameter is given upon initialization, the function also validates that the token is for the required scope.

        • token

        • callback - function(errorObject, authenticationData) ** both objects described below

        validateAuthorizationHeader(authHeader, callback)

        A helper function. Allows developer to use it without having to parse the 'Authorization' header to retrieve the token

        • authHeader

        • callback - as described above

        For example:

        // Load the certificate from a PEM encoded file var cert = require("fs").readFileSync('cert.pem');
        // The scope to mandate (can be null, in which case the token is only checked for a suitable signature and token expiration)
        var scope = "WorlightSecurityTest";
        // Create a reusable validator
        var worklightValidator = require("worklight-access-token-validator")(cert, scope);
        worklightValidator.validate(token, function(error, payload) {
          if (error) {
            // Token is invalid, send appropriate response to user     response.writeHead(error.httpStatus,{"WWW-Authenticate":error.wwwAuthenticateHeader});
          } else {
            // Token is valid, proceed with request
        });

        The following table lists the fields that the error object in the callback method contains:

        Field Explanation
        err

        Contains the error code, which can be one of the following:

        • 'invalid_token'

        • 'missing_token'

        • 'insufficient_scope'

        errMsg Contains the human-readable description of the error reason.
        httpStatus HTTP status to use when responding to the access token sender.
        wwwAuthenticateHeader Content of the 'WWW-Authenticate' header that should be responded to the access token sender.

        The authentication data in the callback method contains the following fields:

        Field Explanation
        version Version of the token.
        scope Security test that the token authenticated.
        expiration Time (in milliseconds) since epoch when the token expires.
        data

        Object with the following fields.

        user_id [optional]

        Authenticated user

        device_id [optional]

        device id as known by MobileFirst Server

        application_id

        identity of the app

    • Option iii: Configure the external service using a validation endpoint.

      When offline validation is inappropriate, we can use online validation of the access tokens with the /oauth/validation endpoint. This endpoint provides the signature validation, expiration check, and optional scope validation.

      For this example (in pseudo-code), the optional scope parameter will not be passed to the endpoint.

      filter(request):
        header = getHeader(request, 'Authorization')
        // header should have the format: "Bearer <token>"
        token = parseHeader(header)
       
        // Call validation endpoint as described   res = callValidationEndpoint(token)
        if (res.code != SUCCESS) {
          // Token not validated, reponse can be sent as is to caller
          sendResponse(res)
          exit
       
        // If successful
        payload = res.body
        scope = payload.scope
        userId = payload.data.user_id
        // Continue with scope checking | passed filter, return payload/data


What to do next

Consider using the client-side API features that support the use of MobileFirst access tokens.

Client-side API

The WL.Client API offers built-in support for using MobileFirst access tokens for the following platforms:

  • Hybrid: JavaScript

  • Android

  • iOS

Use the methods included in this API in the following ways:

Obtaining and caching a token for a specified scope

WL.Client requests a new token from the MobileFirst Server. To obtain the token, the client must be authenticated in all realms of the requested scope (which is represented by a security test in MobileFirst Server's AuthenticationConfig.xml file). Thus, calling this method might trigger an authentication sequence for all realms for which authentication is still required.

This method is asynchronous in all platforms. It does not return a value, but instead triggers a response handler. Note that there is no need to parse the response from the server in the response handler. The token is automatically parsed and cached inside WL.Client and can be retrieved using the following method:

Get the last obtained access token

WL.Client returns the last access token for a certain scope. Alternatively, if no scope is provided, the last obtained token is returned. This is useful when an application is only using one scope.

The scope is represented as a string and should be added to the "Authorization" header of the request to the protected external server, preceded by "Bearer". The following example demonstrates how you might do this when issuing an ajax request:

var token = WL.Client.getLastAccessToken();
  $.ajax({
    type : "GET", url : MY_URL, headers : {"Authorization" : "Bearer " + token}
    })

Get the required scope from the external service response

When a request to the external service fails, WL.Client is able to identify whether the failure is related to access token issues (for example, the token does not match the required scope, the token has expired), and will return the name of the scope required in order to access the service. In this case, obtaining a new token for the returned scope is required. If the error is not related to access token issues, this method returns null.

Suggested use of client-side API

This JavaScript example shows how to use the client-side API when accessing an external service:

function callProtectedRestAPI(retries) {
 
  // We want to be able to call this method recursively, since in some cases
  // we need to obtain a new token, and try a second time.
  if (retries == 0) {
  return;
 
  // Get the last obtained access token. 
  // On the first call, the token may be null.
  var token = WL.Client.getLastAccessToken();
  var headersObject = (token != null) ? {"Authorization" : "Bearer " + token} : {};
 
  $.ajax({
    type : "GET", url : MY_EXTERNAL_SERVER_URL, headers : headersObject
  
  }).done(function(response) {
    showResult(response);
  }).fail(
    function(response) {
      // We need to extract this header from 
      // the response in order to know the scope.
      var header = response.getResponseHeader("WWW-Authenticate");
      var scope = 
        WL.Client.getRequiredAccessTokenScope(response.status,header);
      if (scope != null) {
    
        // The failure is related to the access token. Get a new one.
        WL.Client.obtainAccessToken(
          scope, getTokenSuccess,getTokenFailure);
      } else {
        showErrorResult("request failed");
  });
  function getTokenSuccess(response) {
  
    // We obtained a token. try to access the external server one more time.
    callProtectedRestAPI(retries - 1);
  function getTokenFailure(response) {
  showErrorResult(response);
}

WL.Client API reference information

For more information about the WL.Client API, see the following sections:

Exposed endpoints

This feature exposes two new endpoints of the MobileFirst Server:

/oauth/token

Used from the MobileFirst app in order to authenticate for a desired scope. This endpoint should be used with one of the client-side APIs provided. (See section WL.Client API reference information earlier in this topic for a list of links to WL.Client API reference information.)

Return:

  • If authentication fails, return code and appropriate error message is returned as defined by Oauth 2.0 RFC.

  • If authentication flow is completed successfully, returns a valid access token.

/oauth/validation

This endpoint can be used to validate an access token which was created by the same MobileFirst Server. A valid request will have the following properties:

Property Description
URL <context-root>/oauth/validation
Method POST
Parameter: token (mandatory) The token in question.
Parameter: scope (optional) The scope that protects the resource.

Regardless of whether scope is supplied or not, the endpoint makes sure that the token is valid. If the optional parameter scope is provided, the endpoint verifies that the token is provided for the required scope.

Return:

  • If validation fails, return code & appropriate header will be returned as defined by Oauth 2.0 RFC for the Bearer token.

  • If successful, the payload of the token is returned to user. The payload is a JSON object, and as of token version WL1.0, its format is:
    {
      version: 1.0 (version of token)
      scope: <The security test that the token authenticated against>
      expiration: <Time in msec since epoch when token will be expired>
      data: {
        user_id: <authenticated user>
        device_id: <device id as known by Worklight server>
        application_id: <identity of the app>
    }


Parent topic: MobileFirst security framework