QR Device Registration Example

This shows some capabilities of the Extension Engine and how you might implement a QR Device registration. The scripts are based on the following flow:

  1. User logs in on the website or portal with their credentials
  2. Once logged in, user selects register device option in the portal
  3. Website calls the Onegini Token Server on the backchannel endpoint
  4. The Onegini Token Server triggers the backchannel script for this Identity Provider (CUSTOM_API_ONE_STEP) and returns the result to the website
  5. Website generates a QR code based on the response
  6. User scans this QR code with the mobile app that is not registered yet
  7. Mobile app calls the Onegini Token Server on the complete endpoint
  8. The Onegini Token Server triggers the complete script and returns the result to the mobile app
  9. User is asked to create a new PIN code in the mobile app if the result is successful

Backchannel Script

The backchannel script is the first part of QR registration. It is used as a way to interact with the Extension Engine before the actual user gets involved with their app. You can utilize this script to store data that will be fetched later. In our example, we send a userId to be stored as well as the callbackUrl that is used to notify the caller as soon as the complete script is executed. Below is a sample string that sends a userId and callbackUrl in the requestPayload.

Example request to the backchannel script:

{
    "data": "{\"userId\":\"exampleUserId\", \"callbackUrl\":\"https://portal.onegini.com/callback\"}"
}

Example Script

function execute(requestPayload){
    var userId = JSON.parse(requestPayload).userId;
    var callbackUrl = JSON.parse(requestPayload).callbackUrl;
    var identifier = java.util.UUID.randomUUID().toString();
    // Store any data you need
    CACHE.store(identifier, userId, 35); //sets TTL to 35 seconds for this key.
    var transactionID = CACHE.fetch(userId);
    if(!transactionID)
    {
       transactionID = java.util.UUID.randomUUID().toString();
       CACHE.store(userId, transactionID, 300); //Generate transaction for user
       CACHE.store(transactionID, callbackUrl, 300); //Set callback URL
    }
    LOG.info("storing userId: {}", userId);
    return {
      status: 2000,
      responsePayload: JSON.stringify({
         uuid:identifier,
         transaction: transactionID,
         lifetime:30
      })
    };
}

The syntax used in the script above is described in more detail in writing scripts section. It utilizes the cache and sets a specific TTL. For more information involving the cache and default TTLs, refer to the cache documentation.

Example response

Here is an example response you'd get from the script above. As part of the QR flow, you should parse the identifier and then embed it in the QR code.

{
   "data": {
     "uuid": "e2048242-085f-4210-93ff-84df1fcd8ce2",
     "transaction": "24f8c67e-5957-11eb-ae93-0242ac130002",
     "lifetime":30  
  }, 
   "status": 2000
}

The data#uuid field contains the identifier that is meant to be embedded in the QR code and provided to the mobile SDK.

Complete Script

The complete script is the second part of QR registration. In this step, the mobile app will allow the user to scan the generated QR code, parse the data embedded in it, and then send that on to the SDK. In our example, the identifier json is sent with the complete request so it can be used to fetch the userId and the callbackUrl that were stored earlier. The json below is a sample of what you would need to send to the SDK. The SDK will automatically escape it when it sends the request to the Onegini Token Server.

Example String sent to SDK

{
    "identifier": "e2048242-085f-4210-93ff-84df1fcd8ce2"
}

Example Script

function execute(requestPayload){
      var postData = function(url,data){
       var headers = new org.springframework.http.HttpHeaders();
       headers.add('Content-Type', 'application/json');
       var entity = new org.springframework.http.HttpEntity(data, headers);
       REST_TEMPLATE.postForEntity(url, entity, java.lang.String.class);
    }
    var identifier = requestPayload;
    var userId = CACHE.fetch(identifier);
    LOG.info("retrieved from cache: {}", userId);
    // You may want to delete the entry so the same request cannot be made again
    var status = 2000;
    if (userId){
      var transactionID = CACHE.fetch(userId);
      LOG.info(transactionID);
      var callbackUrl = CACHE.fetch(transactionID);
      LOG.info(callbackUrl);
      LOG.info(JSON.stringify({ "transactionId": transactionID }));
      var body = JSON.stringify({"transactionId":transactionID});
      postData(callbackUrl, body);
      CACHE.delete(userId);
      CACHE.delete(transactionID);
      CACHE.delete(identifier);
    } else {
      status = 5000;
    }
    return {
      status: status,
      user: {
        id: userId
      }
    };
}