Integration overview - server side

This section describes the steps to integrate PingID SDK in a customer application server:

  1. Download the PingID SDK package
  2. Setup your PingID SDK account / Download the PingID SDK settings file
  3. Setup your application
  4. Integrate the server side
  5. Integrate pairing and authentication flows

Download the PingID SDK package

The PingID SDK download package contains:

  • The PingID SDK component for iOS and Android applications.
  • The PingID SDK sample customer server source code.
  • The PingID SDK sample customer server war file.
  • The PingID SDK demo app source code (Moderno).

Download the PingID SDK package from PingID downloads.

Setup your PingID SDK account / Download the PingID SDK settings file

The PingID SDK account administrator will need to download the account’s settings file. This file contains several account-specific settings, including the values that you’ll need to use when creating a PingID SDK API request message.

The PingID SDK settings file

Property Name Description
api_key A key that you'll use to create a signature value for each message.
token Unique identifier of the PingID SDK client.
pingidsdk_url
account_id The ID of the PingID SDK tenant.
api_key A key that you’ll use to create a signature value for each message.

For security purposes, all PingID SDK requests are validated using the token and api_key values, and are rejected if these values are invalid.

Setup your application

As part of the integration of a customer’s mobile application with the PingID SDK component, the PingID SDK account administrator should create an application in the PingOne account.

During this process, an application ID will be generated. This application ID is used for identification of the customer mobile application in PingID SDK. it should be used by the customer server and the customer mobile application.

Integrate the server side

Authorization header in the request

The authorization header contains information about the client and a sent request. The authorization header is in JWT format. A JWT is represented as a sequence of URL-safe parts separated by period (’.’) characters.

A response of each authenticated request will be signed by the PingID SDK service. We recommend verifying the signature of a response, as described in Signatures in PingID SDK.

Integrate pairing and authentication flows

Determine which type of pairing you will allow your users. Pairing types are described in greater detail in Registration process.

This section presents examples of the following flows:

  • Automatic pairing integration
  • Manual pairing integration
  • Authentication

Automatic pairing integration

Choose this kind of pairing if you would like the registration process to be done seamlessly behind the scenes.

We suggest the following set and order of actions for the server side.

  • Automatic pairing pseudocode:

    1.  GET user resource by user name from PingID SDK service
    2.  if user in PingID SDK service doesn't exist
    3.    create user resource in  PingID SDK service 
    4.  fi
    5.  if user status is NOT active
    6.    if accessing device is without PingID SDK mobile payload
    7.       show user the message "Please install the latest version of the app and log in"
    8.       exit
    9.    fi
    6.    create RegistrationToken resource
    7.    add payload from RegistrationToken resource to server response
    8.    exit
    9.  fi
      
  • Automatic pairing: Java sample:

    protected Status authenticate() throws Exception {
        
        // Authenticate with the customer server (1st factor). this is for the demonstration.
        // Usually, authentication with PingID SDK is a second factor authentication  
        // (that is, the user is first authenticated with the user credentials, and then as a second factor, authenticated with PingID SDK).
        Status status = firstFactorAuthentication();
        if (status != Status.OK) {
          return status;
        }
    
        logger.info(String.format("User passed 1st factor authentication. username: %s", username));
    
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // This section demonstrates how this customer server integrates its logic with the PingID SDK solution //
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
        try {
          // At this stage, the user has passed the customer server first factor authentication
    
          // Step (1): Check if the user exists in PingID SDK service.
          // If the user does not exist, create the user in the PingID SDK server
          User user = getUserOrCreateIfUserNotExist(username);
    
          // At this stage, the user exists in PingID SDK Server DB
    
          // Step (2): Check if the user is active. If the user has at least one paired device, the user is active.
          boolean isUserActive = isUserActive(user);
    
          // Automatic Pairing: If the user is not active, create a Registration Token and return it to the caller.
          if (!isUserActive) {
    
            // case 1: call is from the mobile application. (pingIdPayloadMobile is not null or empty)
            // The pingIdPayloadMobile is the payload which is generated by the mobile PingID SDK 
            // component. Each request which is originated from a mobile application (integrated with PingID 
            // SDK) must contain this payload.
            if (pingIdPayloadMobile != null && !pingIdPayloadMobile.trim().isEmpty()) {
              RegistrationToken regToken = createRegistrationToken(user, pingIdPayloadMobile);
    
              // The response must contain the Registration Token ID and the server payload.
              // The mobile application will pair the user device using this data.
              // In this example, the authentication response is defined at class level
              authenticationResponse.setRegistrationToken(regToken.getId());
              authenticationResponse.setPingIdPayload(regToken.getPayload());
              return status;
            }
    
            // case 2: call is not from the mobile application.
            // It is impossible to create a Registration Token if the caller is not the mobile application.
            return Status.USER_NOT_ACTIVE;
          }
    
          // isUserActive == true. If the program reached this line, the user is active.
    
          // Step (3) authenticate the user with PingID SDK
          // Add logic here to continue to the authentication steps with PingID SDK... 
          // (see the authenticate() example in the Authentication section below)
        return status;
    }
      
    private Status firstFactorAuthentication() {
        return Status.OK; // Each customer server may implement a first factor authentication (if needed)
    }
      
    private User getUserOrCreateIfUserNotExist(String username) throws PIngIDSdkException{
        User user = getUserFromPingIDSdk(username);
    
        if (user == null) {
          user = addUserToPingIDSdk(username);
        }
        return user;
    }
      
    private User getUserFromPingIDSdk(String username) throws PIngIDSdkException {
        String url = String.format("/accounts/%s/applications/%s/users/%s", accountId, applicationId, username);
        // apiHelper is a helper class with the purpose of sending requests to PingID SDK service 
        User user = apiHelper.get(User.class, url);
        return user;
    }
    
    private User addUserToPingIDSdk(String username) throws PIngIDSdkException {
        String url = String.format("/accounts/%s/users", accountId);
        User user = new User();
        user.setUsername(username);
        User createdUser = apiHelper.post(User.class, url, user);
        return createdUser;
    }
      
    private boolean isUserActive(User user) {
        return user.getStatus() != null && user.getStatus().equals(UserStatus.ACTIVE);
    }
      
    private RegistrationToken createRegistrationToken(String username, String pingIdPayloadMobile) throws Exception {
        String url = String.format("/accounts/%s/applications/%s/users/%s/registrationtokens", accountId, applicationId,username);
        RegistrationToken regToken = new RegistrationToken();
        regToken.setPayload(pingIdPayloadMobile);
        RegistrationToken createdRegToken = apiHelper.post(RegistrationToken.class, url, regToken);
        return createdRegToken;
    }

Manual pairing integration

Manual pairing integration is separated into 2 parts:

  • Creation of a Pairing Key resource
  • Validation of the pairing key, and subsequent actions of the pairing process

Creation of a Pairing Key resource:

  • Manual pairing: pseudocode for creating a pairing key:

    1. Create a Pairing Key resource
    2. Add the data to the pairing key.
       This data contains information that allows the client to validate a specific user.
    3. POST Pairing Key resource to PingID SDK service
      
  • Creating a pairing key: Java sample:

    protected Status processRequest() {
        try {
           status = createPairingKey();
        } catch (Exception e) {
            status = Status.FAILED_TO_CREATE_PAIRING_KEY;
        } 
        return status;
    }
    
    private Status createPairingKey() throws Exception {
        // A pairing key may have pairing data (this is not mandatory):
        // Pairing data can be anything which the customer server needs in order
        // to validate the pairing key.
        // In this specific example, the customer server creates a list of the users for whom the
        // pairing key is valid 
        // (the getGroupUsers() is not described here since this is just an example.)
        // Each customer server can create the pairing data as it needs
        List<String> users = getGroupUsers();
        Gson gson = new Gson();
        String pairingData = gson.toJson(users.toArray());
        
        PairingKey createdPairingKey = PingidSdkAPIUtils.createPairingKey(accountId, applicationId,
     pairingData);
        return Status.OK;
    }

Validating a Pairing Key resource:

  • Manual pairing: pseudocode for validating a pairing key:

    1.  GET Pairing Key resource by pairing_key
    2.  if resource doesn't exist
    3.    show user an error "Contact your administrator to receive a paring key for 2FA"
    4.    exit
    5.  fi
    6.  get field 'data' from the Pairing Key resource
    7.  run a customer's validation logic
          the customer's server should check that user matches the pairing key
    8.  if the user is NOT validated
    9.    show the user an error "Contact your administrator. Your paring key is invalid"
    10.   exit
    11. fi
    12. Code from automatic pairing flow can be added here.
      
  • Manual pairing: Java sample:

    protected Status pair() {
                // checks if the user successfully passed first factor authentication.
                // In this example, the customer server validates that the user credentials are correct
                // before trying to pair the user
                if (!userFirstFactorAuthenticated()) {
                   return Status.NOT_AUTHENTICATED;
                } 
                
                
                // retrieve the pairing key from the server 
                PairingKey pairingKeyInServer =
             PingidSdkAPIUtils.getPairingKey(accountId, applicationId,
             pairingKey);
             
                // the provided pairing key does not exist
                if (pairingKeyInServer == null) {
                  return Status.PAIRING_KEY_NOT_EXIST;
                }
                
                // The customer server can validate that this pairing key is valid
                // for the user.
                // For example, when creating the pairing key, the customer server can set
                // the pairingKey.pairingData member to be the users list for whom the pairing key is valid.
                // for example: user1,user2,user3.
                // When validating the pairing key, the customer server can check if pairingData contains the user.
                // Note: this is just an example. The pairingData can contain any data and it can be null  
                if (!isPairingKeyValidForUser(pairingKeyInServer)) {
                    return Status.INVALID_PAIRING_KEY;
                } 
                
                // In this stage, the pairing key is considered valid and the user pairing process begins:
                User user = createUserIfNotExist();
                // Check if the user is active. If the user is active, exit this flow with the relevant status.
                boolean isUserActive = isUserActive(user);
                if (isUserActive) {
                  return Status.USER_ALREADY_ACTIVE;
                } 
                
                // user pairing can take place only if the request contains the mobile payload
                // that is, the request originated from the mobile  
                if (pingIdPayloadMobile != null && ! pingIdPayloadMobile.trim().isEmpty()) {
                   String url = String.format("/accounts/%s/applications/%s/users/%s/registrationtokens", accountId, applicationId,username);
                   RegistrationToken regToken = new RegistrationToken();
              regToken.setPayload(pingIdPayloadMobile);
                 // set the pairing key
              regToken.setPairingKey(pairingKey);
                   RegistrationToken createdRegToken = apiHelper.post(RegistrationToken.class, url, regToken);
                
                   // The response must contain the RegToken itself & the server payload.
                   // The customer mobile application will pair the user device using this data.
                   // In this example, the authenticarion response is defined at class level
                   pairResponse.setRegistrationToken(regToken.getId()); 
                   pairResponse.setPingIdPayload(regToken.getPayload());
                   return Status.OK;
                }   
                // call is not from the customer mobile application. pairing cannot take place
                return Status.USER_NOT_ACTIVE;
            }

Authentication

  • Authentication pseudocode:

1.  GET User resource by user name
2.  if user doesn't exist or user status is NOT active
3.     commands here depend on which pairing flow was selected
4.  fi
5. create authentication resource
6. analyse received authentication resource
7. if status of Authentication resource is IN_PROGRESS
8. loop
9.   GET Authentication by authentication id
10.   if (status of Authentication resource is NOT IN_PROGRESS)
11.      exit from the loop
12.   fi
13. loop end
14. add the server payload from Authentication resource to the server response.
15. client should decide what to do next after analyzing Authentication response
  
  • Authentication: Java sample:
protected Status authenticate() {
	   
    // Step (1) 1st factor. The customer server should authenticate the user.
    Status status = firstFactorAuthentication();
    if (status != Status.OK) {
        return status;
    }
   
    // Step (2) Steps here depends on which pairing flow you want to choose.
   
    // Step (3) The user should be active (if we reach this line). It means the customer
    // server may authenticate the user with PingID SDK
   
    Authentication authentication = authenticateWithPingIDSdk();
   
    // Step (4) handle the returned authentication results.
          
    // If the authentication is still in progress, the customer server should poll the PingID SDK service
    // (sending "GET" authentication requests) until final status is returned.
    // Each customer server can handle it in a different way.
    // In this sample, the customer server polls until a final status is returned
    if (authentication.getStatus() == AuthenticationStatus.IN_PROGRESS) {
        authentication = pollPingIdUntilFinalStatus(authentication.getId());
    }
   
    // The response must contains the server payload (if exists) (so that the mobile PingID SDK can handle it)
    // Any customer server implementation must return the server payload (if it exists)in the response 
    authenticationResponse.setPingIdPayload(authentication.getPayload());
   
    // convert the authentication status to the customer server status.
    status = convertPingIdStatus(authentication.getStatus());
   
    return status;
}
   
private Authentication pollPingIdUntilFinalStatus(String authenticationId) {
    String accountId = PingidSdkConfiguration.instance().getAccountId();
    String applicationId = PingidSdkConfiguration.instance().getAppId();
    String url = String.format("/accounts/%s/applications/%s/users/%s/authentications/%s", accountId, applicationId, username, authenticationId);
    while (true) {
        Authentication authentication = PingidSdkAPI.instance().get(Authentication.class, accountId, url);
        if (authentication.getStatus() != AuthenticationStatus.IN_PROGRESS) {
            return authentication;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
    }
}
   
public static Status convertPingIdStatus(AuthenticationStatus authenticationStatus) {
    switch (authenticationStatus) {
        case APPROVED:
            return Status.OK;
        case LOCKED:
        case OTP_IS_BLOCKED:
        case REJECTED:
            return Status.AUTHENTICATION_DENIED;
        case OTP:
            return Status.OTP;
        case IN_PROGRESS:
            return Status.AUTHENTICATION_IN_PROGRESS;
        case SELECT_DEVICE:
            return Status.SELECT_DEVICE;
        case IGNORED_DEVICE:
            return Status.IGNORED_DEVICE;
        default:
            return Status.FAILED;
    }
}