PingID SDK demo server developer guide

The PingID SDK example customer server (demo server) demonstrates how to integrate PingID SDK capabilities into an existing customer server logic. This example server simulates a fictional company (“Moderno”) which allows users to transfer money within the user’s account. The following flows are demonstrated in the example:

  1. User login:

    The user enters a user name and a password. The example customer server simulates first factor validation (placeholder for 1st factor) and, assuming the user credentials are correct, the example customer server executes MFA with PingID SDK, by authenticating the user with the PingID SDK server.

  2. Step up:

    When the user tries to perform a high value transaction such as transfer funds, the example customer server authenticates the user with the PingID SDK server by sending him a push notification to his trusted mobile device, which includes transaction information.

The example customer server can be divided into 2 sections:

  1. The “Moderno” section, which contains the “Moderno” logic.
  2. The PingID SDK section, which contains the PingID SDK integration logic. This can be found in the com.pingidentity.pingidsdk package. In addition, the PingID SDK API model can be found in the com.pingidentity.pingidsdk.api package.

Prerequisites

  1. PingOne account

    • It is assumed that an account already exists with an enabled PingID SDK application. This can be implemented in the PingID administrator web portal.

    • It is assumed that the account secret key and the account token are already known, and can be downloaded from the administrator web portal.

    • This example reads the account ID, application ID, api key and token from a properties file which should be located under the /env/hs-props folder. The properties file content should be in the following format:

      api_key=<<< the account secret key >>>
       token=<<< the account token >>>
       account_id=<<< the account ID >>>
       app_id=<<< the application ID >>>
  2. The PingID SDK package, downloaded from PingID Downloads.

Getting started

Customer server example structure

The com.pingidentity.pingidsdk package contains the classes which demonstrate the logic which communicates with the PingID SDK server. This package contains 3 classes:

  1. PingIDSdkHelper: Class which contains the logic for authenticating a user and creating a registration token for the user.
  2. PingIDSdkAPI: Class which represents the layer which is responsible for sending requests to the PingID SDK server.
  3. PIngIDSdkExcpetion: Wrapper for exceptions returned from the PingID SDK server.

The com.pingidentity.pingidsdk.api package contains the PingID SDK resource classes.

The rest of the packages contains the “Moderno” example’s specific customer server logic.

Integrate PingID SDK logic into your customer server code using this example:

You may have your own logic for authenticating a user, for example, a user may enter his user name and password in order to authenticate.

PingID SDK authentication can be served as a second factor authentication, or even as a first factor. It is up to each customer server to decide when, and if, to authenticate a user using PingID SDK. In this example, the user first needs to pass the first factor authentication, and then to be authenticated with PingID SDK. It is important to understand that a user must have at least one paired device in order to authenticate with PingID SDK

In general, the customer server logic uses the PingIDSdkHelper for integration with PingID SDK. It initializes this helper with the account id, the account secret key, the account token and the application id.

You cannot send any requests to PingID SDK without the the account id, the account secret key, and the account token. Most of the requests also require the application id.

Let’s observe how this customer server uses PingID SDK:

  1. The customer server validates that the user passes the first factor authentication. So if your customer server validates the user credentials, you can first validate your user credentials and only then authenticate the user with PingID SDK. As already mentioned, each customer server can decide when and if to authenticate the user with PingID SDK server.

  2. The customer server checks if the user exists in PingID SDK server DB, and creates the user if he doesn’t exist.

    It uses the following method defined in the PingIDSdkHelper class:

    public User getUserOrCreateIfUserNotExist(String username) throws PIngIDSdkException{
    		User user = getUserFromPingIDSdk(username);
     
    		if (user == null) {
    			user = addUserToPingIDSdk(username);
    		}
    		return user;
    }

    A user cannot pair any device or authenticate if he does not exist in the PingID SDK server DB. In order to check if the user exists in the PingID SDK DB, the example uses the following method:

    private User getUserFromPingIDSdk(String username) throws PIngIDSdkException {
        String url = String.format(USERS_GET_URL, accountId, applicationId, username);
        // the apiHelper is an instance of PingIDSdkAPI class which is a layer which sends requests to
        // PingID SDK server
        User user = apiHelper.get(User.class, url);
        return user;
    }

    In order to add a user to the PingID SDK DB, the example uses the following method:

    private User addUserToPingIDSdk(String username) throws PIngIDSdkException {
    		String url = String.format(USERS_POST_URL, accountId);
    		User user = new User();
    		user.setUsername(username);
    		user.setFirstName(""); // optional (in this example, no first name)
    		user.setLastName(""); // optional (in this example, no last name)
    		User createdUser = apiHelper.post(User.class, url, user);		
    }
  3. The customer server checks if the user is active; that is, if the user has at least one paired device with this application. It uses the following method, defined in the PingIDSdkHelper class:

    public boolean isUserActive(User user) {
    	return user.getStatus() != null && user.getStatus().equals(UserStatus.ACTIVE);
    }
  4. If the user is not active, the customer server creates a registration token for the user. However, the customer server can create a registration token for the user only if the customer server receives the request from a mobile application which is already integrated with PingID SDK.

    Such a request contains a payload which is generated in the mobile PingID SDK component.

    When integrating your customer server code with PingID SDK, you must distinguish between calls which contain this payload, and those which do not.

    As mentioned, it is impossible to create a registration token for a user without this payload, for example, if the request originates in the web.

    This customer server example returns a dedicated status if there is no payload. It is up to each customer server to decide how to deal with an inactive user if the request does not contain a payload. For example, your customer server logic can show a message encouraging the user to install the upgraded mobile application, which is already integrated with PingID SDK.

    The following example demonstrates a user who is not active, and the request contains a payload. The customer server creates a registration token, using the following method, defined in the PingIDSdkHelper class:

    private RegistrationToken createRegistrationToken(String username, String pingIdPayloadMobile) throws PIngIDSdkException {
    		String url = String.format(REGISTRATIONS_TOKENS_POST_URL, accountId, applicationId,username);
    		RegistrationToken regToken = new RegistrationToken();
    		regToken.setPayload(pingIdPayloadMobile);
    		RegistrationToken createdRegToken = apiHelper.post(RegistrationToken.class, url, regToken);
    }

    The returned RegToken object contains:

    • The registration token.
    • The server payload. The token and the server payload must be returned in the response from the customer server to the mobile application, so that the PingID SDK component which is integrated into your mobile application will be able continue the user device pairing process.

    This approach is referred to as “Automatic Pairing”, which means the device pairing takes place without user awareness.

    Another approach is to pair the user manually. Pairing the user manually is not demonstrated in this customer server example.

  5. In the following example, if the user is already active (the user has at least one paired device), the customer server authenticates the user with PingID SDK. It uses the following method, defined in the PingIDSdk class:

    private Authentication authenticateWithPingIDSdk(String username, String pingIdPayloadMobile, String deviceId, String customizedPushTitle, String customizedPushBody, String clientContext) throws PIngIDSdkException {
    		String url = String.format(AUTHENTICATIONS_POST_URL, accountId, applicationId,
    				username);
    		Authentication authentication = new Authentication();
    		authentication.setDeviceId(deviceId); // may be null
    		authentication.setPayload(pingIdPayloadMobile); // null if the call is not from the mobile application
    		authentication.setPushMessageTitle(customizedPushTitle);
    		authentication.setPushMessageBody(customizedPushBody);
    		authentication.setClientContext(clientContext);
    		authentication.setAuthenticationType(AuthenticationType.AUTHENTICATE);
    		Authentication createdAuthentication = apiHelper.post(Authentication.class, url, authentication);
    		return createdAuthentication;
    	}
    • The username is mandatory.
    • If the request is not from the mobile application, the payload should be null.
    • The device ID is optional. You may authenticate the user with a specific device if you wish.
  1. The following example demonstrates how to get the user devices, using the following method defined in the PingIDSdkHelper class:

    public List<Device> getUserDevices(String username) throws PIngIDSdkException {
    		String url = String.format(USERS_GET_URL, accountId, applicationId, username);
    		UserWithDevices userWithDevices = apiHelper.get(UserWithDevices.class, url,"devices");
    		return userWithDevices.getDevices();
    }

Best practices for security

Ping Identity does not control the channel between the customer erver and the customer mobile application, but uses it to transfer data from the PingID SDK component in the customer mobile application to the PingID SDK server. Therefore our clients are advised to use best practices in order to secure the channel between the customer server and customer mobile application, so confidentiality of the PingID SDK payloads won't be compromised.

Ping Identity strongly recommends that implementers use Transport Layer Security (TLS) to protect the network traffic between their mobile applications and their customer servers. Implementers should ideally only allow the use of TLS 1.2 and later, disabling support for older protocols, and should avoid the use of 3DES or RC4-based cipher suites. Implementers are encouraged to support DHE (Diffie Hellman Ephemeral) cipher suites and, if possible, use them exclusively, since these cipher suites provide perfect forward security. Finally, if possible, the hosted application should implement certificate pinning or public key pinning, to make it more difficult for attackers to perform man-in-the-middle attacks. For more information on TLS implementation best practices, see: https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet.

Notes

  1. PingID SDK authentication call is asynchronous. In most cases, creating an authentication call returns an authentication object with the “IN PROGRESS” status. In the following example, PingIDSdkHelper polls the PingID SDK server until it receives a final status.

    public Authentication authenticate(String username, String pingIdPayloadMobile, String deviceId, String customizedPushTitle, String customizedPushBody, String clientContext)
    			throws PIngIDSdkException {
    		// Step (1) authenticate the user with PingID SDK
    		Authentication authentication = authenticateWithPingIDSdk(username, pingIdPayloadMobile, deviceId, customizedPushTitle, customizedPushBody, clientContext);
    		// Step (2) handle the returned authentication results.
    		// if the authentication is still in progress , the customer server
    		// should poll the "PingID SDK" server
    		// (sending "get" authentication requests) until final status is
    		// returned.
    		// Each customer server can handle it in a different way.
    		// For example, the customer server can return the call and poll in a
    		// different thread.
    		// In this example, the customer server polls until a final status is
    		// returned
    		if (authentication.getStatus() == AuthenticationStatus.IN_PROGRESS) {
    			authentication = pollPingIDSdkUntilFinalStatus(username, authentication.getId());
    		}
    		return authentication;
    }
    private Authentication pollPingIDSdkUntilFinalStatus(String username, String authenticationId) throws PIngIDSdkException {
    		String url = String.format(AUTHENTICATIONS_GET_URL, accountId, applicationId, username, authenticationId);
    		while (true) {
    			Authentication authentication = apiHelper.get(Authentication.class, url);
    			if (authentication.getStatus() != AuthenticationStatus.IN_PROGRESS) {
    				logger.info(String.format("Authentication Status: %s for username: %s", authentication.getStatus(), username));
    				return authentication;
    			}
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				logger.info("unexpected Interrupt...");
    			}
    		}
    	}
  2. The authentication object status indicates the current authentication status. Your customer server should observe the status and act accordingly. For example, AuthenticationStatus.APPROVED means that the user is authenticated successfully.

    Review the documentation for a full understanding of the various possible statuses.