Skip to content

Front-end implementation

In this section, we will show the integration implementation for the front-end of the merchant application using our sample code.

The following files in the sample code package are essential for 3DS2 authentication in the front-end. Check the directory tree for details if required.

  • process.html: Implements all the authentication sequences.
  • 3ds-web-adapter.js: The core component of the 3DS Client that passes 3DS2 data from the front-end to the back-end and establishes the required iframes for callback URLs.
  • notify_3ds_events.html: Call back page used for ActiveServer to trigger the next step in authentication (Step. 7 and Step. 18(C)). This page delivers authentication events to the checkout process.

Following is details of the front-end implementation based on the authentication processes and authentication sequence.

Process 1: Initialise the Authentication

To initialise an authentication, the front-end needs to:

  • Send an Initialise authentication message to the 3DS Requestor (Step. 1 and Step. 2).
  • Receive the response message and setup a callback iframe (Step. 5 and Step. 6).

When a user clicks the "Continue to checkout" button in the checkout page, the browser stores the necessary data to session storage and goes to the process.html page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//checkout.html
function checkout() {
  var sessionData = {};
  sessionData.channel = "brw";
  sessionData.messageCategory = "pa";
  sessionData.authData = genAuthData();
  sessionData.backButtonType = "toShop";

  sessionStorage.setItem("sessionData", JSON.stringify(sessionData));
  window.location.href = "/process";
}

Note

The sessionData contains:

  • channel: brw, 3ri or enrol. Here we used the brw channel for a browser-based authentication.
  • messageCategory: either pa - payment authentication or npa - non-payment authentication. Here we use pa as an example.
  • authData: all the necessary data in JSON format. For the data structure, refer to the API document.
  • backButtonType: indicates the back button type in the process page. Here we make the back button as Back to shop.

NOTE: The use of sessionStorage for inter-page communications is just for demo purposes. The actual implementation of 3DS Requestor should choose the best approach for transferring parameters between pages for the integration with the existing checkout process.

In the process.html page, it receives the sessionData and starts the 3DS2 processes. Firstly, it sends information for initialising an authentication to the 3ds-web-adapter (Step. 1).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//process.html
var sessionData = JSON.parse(sessionStorage.getItem("sessionData"));
...
switch (sessionData.channel) {
  case "brw":
    var container = $('#iframeDiv');
    brw(sessionData.authData, container, _callbackFn, sessionData.messageCategory,
      sessionData.options);
    break;
  ...     

Note

The brw() function in 3ds-web-adapter takes the following parameters:

  • authData: all the necessary data in JSON format. For data structure, refer to the API document.
  • container: pre-defined container for the 3ds-web-adapter to generate iframes.
  • callbackFn: call back function to deal with the authentication results.
  • messageCategory: either pa - payment authentication or npa - non-payment authentication.
  • options: optional parameters for 3DS Requestor. For example, the 3DS Requestor can choose to cancel the challenge by setting options.cancelChallenge=true.

Next, in the 3ds-web-adapter, method brw() sends information to the 3DS Requestor back-end to initialise authentication (Step. 2).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//3ds-web-adapter.js
function brw(authData, container, callbackFn, messageCategory, options) {

  _callbackFn = callbackFn;
  iframeContainer = container;
  if (options) {
    _options = options;
  }
  //generate an random number for iframe Id
  iframeId = String(Math.floor(100000 + Math.random() * 900000));

  //3DS Requestor url for Initialise Authentication
  var initAuthUrl;
  if (messageCategory) {
    if (messageCategory === "pa" || messageCategory === "npa") {
      initAuthUrl = "/auth/init/" + messageCategory;
    } else {
      _onError({"Error": "Invalid messageCategory"});
    }
  } else {
    initAuthUrl = "/auth/init/" + "pa";
  }

  console.log('init authentication', authData);

  //Send data to /auth/init/{messageCategory} to do Initialise authentication (Step 2)
  doPost(initAuthUrl, authData, _onInitAuthSuccess, _onError);
}

The brw() function makes a POST request to the back-end with the /api/v1/auth/init/{messageCategory} API message. The object is posted in JSON format. To check how the back-end handles this request, refer here.

The 3ds-web-adapter uses the _onInitAuthSuccess() function to handle the successful response (Step. 5).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//3ds-web-adapter.js
function _onInitAuthSuccess(data) {
  console.log('init auth returns:', data);

  if (data.threeDSServerCallbackUrl) {

    serverTransId = data.threeDSServerTransID;
    $('<iframe id="' + "3ds_" + iframeId
        + '" width="0" height="0" style="visibility: hidden;" src="'
        + data.threeDSServerCallbackUrl + '"></iframe>')
    .appendTo(iframeContainer);

  } else {
    _onError(data);
  }

  if (data.monUrl) {
    // optionally append the monitoring iframe
    $('<iframe id="' + "mon_" + iframeId
        + '" width="0" height="0" style="visibility: hidden;" src="'
        + data.monUrl + '"></iframe>')
    .appendTo(iframeContainer);

  }
}

The 3ds-web-adapter inserts two hidden iframes into the checkout page (Step. 6). The first one is for (Step. 7) so that the browser information can be collected by the ACS and ActiveServer using the threeDSServerCallbackUrl.

The second one is an optional monitoring iframe to guarantee that an InitAuthTimedOut event will be returned by ActiveServer when any errors occur during browser info collecting or 3DS Method processing. This event has a timeout of 15 seconds.

Info

Step. 1 to Step. 7 of the sequence diagram are implemented by the end of this process.

Process 2: Execute Authentication

To execute authentication, the front-end needs to first finish the browser information collection and (if available), 3DS Method data collecting. After these 2 processes are done, notify_3ds_event.html will notify the 3ds-web-adapter to continue with the authentication. In this process, if for any reason that the data collecting failed or was not able to finish, the separate monitoring iframe (setup in the InitAuth process) will notify the 3ds-web-adapter with an event InitAuthTimedOut, and then the authentication process will be terminated.

The following are the steps to execute the authentication:

  • Implement or re-use the provided notify_3ds_events.html to receive events about data collecting.
  • Send an Execute authentication message to the 3DS Requestor (Step. 8 and Step. 9) once events _on3DSMethodSkipped or _on3DSMethodFinished are notified.
  • Handle the authentication result with frictionless flow or challenge flow (Step. 13, Step. 14(F) or Step. 14(C)).

The notify_3ds_events.html is used to trigger the authentication process (Step. 8). The 3DS Requestor will render notify_3ds_events.html with the variables transId, callbackName and an optional param. To check the back-end implementation, refer here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!--notify_3ds_events.html-->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <title>3DSecure 2.0 Authentication</title>
</head>
<body>

<form>
  <input type="hidden" id="notifyCallback" name="notifyCallback" value={{callbackName}}>
  <input type="hidden" id="transId" name="transId" value={{transId}}>
  <input type="hidden" id="param" name="param" value={{callbackParam}}>
</form>

<script src="https://code.jquery.com/jquery-3.3.1.min.js"
        integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
        crossorigin="anonymous"></script>
<script>
  //notify parent check out page to proceed with rest procedures.

  var callbackFn = parent[$('#notifyCallback').val()];
  //callbackFn is defined in 3ds-notify handler method
  if (typeof callbackFn === 'function') {
    callbackFn($('#transId').val(), $('#param').val());
  }
</script>

</body>
</html>

You can see that depending on the callbackName, it will call different methods in the 3ds-web-adapter (Step. 8). The value of callbackName should be _onThreeDSMethodFinished, _onThreeDSMethodSkipped, _onAuthResult or _onInitAuthTimedOut. An explanation of each method is below:

EventDescription
_onThreeDSMethodFinishedNotifies that the 3DS method is finished by ACS and it's time to call _doAuth()
_onThreeDSMethodSkippedNotifies that the 3DS method has been skipped (Not available or other reasons) and it's time to call _doAuth(),
Note regardless of 3DS Method is skipped or not, the 3DS Server browser information collecting is
always performed prior to 3DS Method.
_onAuthResultThis event notifies that the authentication result is available to fetch.
Used in frictionless and challenge flow (Step. 17(F) & 19(C)).
_onInitAuthTimedOutNotifies that errors occurred during 3DS Method or browser info collecting.
In this demo, the authentication process will be terminated when this event occurs.
The screenshot below shows the error result page:

timeout result

Here, at Step. 8, the callbackName returned by ActiveServer will be either _onThreeDSMethodFinished or _onThreeDSMethodSkipped. The 3ds-web-adapter will call _doAuth() to make a POST request to execute authentication (Step. 9) once these events are received.

The backend then makes a call to API /api/v1/auth/brw. To check how the back-end handles the request, refer here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//3ds-web-adapter.js
function _doAuth(transId) {

  console.log('Do Authentication for transId', transId);

  //first remove any 3dsmethod iframe
  $("#3ds_" + iframeId).remove();
  var authData = {};
  authData.threeDSRequestorTransID = transId;
  authData.threeDSServerTransID = serverTransId;

  doPost("/auth", authData, _onDoAuthSuccess, _onError);
}

The 3ds-web-adapter uses _onDoAuthSuccess() function to handle the successful response (Step. 13).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//3ds-web-adapter.js
function _onDoAuthSuccess(data) {
  console.log('auth returns:', data);

  if (data.transStatus) {
    if (data.transStatus === "C") {
      // 3D requestor can decide whether to proceed the challenge here
      if (_options.cancelChallenge) {
        if (_options.cancelReason) {
          var sendData = {};
          sendData.threeDSServerTransID = serverTransId;
          sendData.status = _options.cancelReason;
          doPost("/auth/challenge/status", sendData, _onCancelSuccess,
              _onCancelError)
        } else {
          var returnData = _cancelMessage();
          _callbackFn("onAuthResult", returnData);
        }
      } else {
        data.challengeUrl ? startChallenge(data.challengeUrl) : _onError(
            {"Error": "Invalid Challenge Callback Url"});
      }

    } else {
      _callbackFn("onAuthResult", data);
    }
  } else {
    _onError(data);
  }
}

It performs different flows based on the returned transStatus.

Note

The transStatus can be Y, C, N, U, A, and R

  • Y: Authentication/Account Verification Successful
  • C: Challenge/Additional authentication is required
  • N: Not Authenticated/Account Not Verified
  • U: Authentication/Account Verification Could Not Be Performed
  • A: Attempts Processing Performed
  • R: Authentication/Account Verification Rejected

For more information related to transStatus, refer to the API document.

Frictionless authentication result

If the transStatus is not C (i.e. frictionless), the 3ds-web-adapter will go to the frictionless flow (Step. 14(F)) and show the results using the _callbackFn("onAuthResult", data) (line 11).

1
2
3
4
5
6
7
8
9
//process.html
function _callbackFn(type, data) {

  switch (type) {
    case "onAuthResult":
      //display "Show results in separate page"
      $("#sepButton").removeClass("d-none");
      showResult(data);
      break;

The showResult(data) function will display the results in the process.html page:

result shortcut

The frictionless authentication process stops at this point. The merchant checkout process can now move to the authorisation process as normal using the authentication result information.

Info

The authentication result can also be shown in a separate page. This is covered in process 3.

Continue challenge process

If the transStatus is C, a challenge is required from the ACS. It is up to the 3DS Requestor to decide whether it wants to continue the 3DS2 authentication and proceed to the challenge process or terminate the authentication process if a challenge is not desired. In this demo, the options.cancelChallenge parameter is used to indicate the 3DS Requestors decision on the challenge flow. This feature is outlined on the BRW Test Page.

Note

For demo purposes, the cancel challenge process is implemented in the front end javascript (3ds-web-adapter). For security reasons this process may need to be implemented in the back end on the production environment.

The 3ds-web-adapter will check the options.cancelChallenge parameter. If options.cancelChallenge=true, the 3ds-web-adapter will cancel the challenge. Optionally, options.cancelReason can also be set and sent to ActiveServer depending on the specified Cancel Reason. The specified reason will be sent by the 3ds-web-adapter to /auth/challenge/status in the back-end. To check how the back-end handles this request, refer here.

The cancel challenge results screen should look similar to the screenshot below:

cancel challenge screen

If options.cancelChallenge=true is not present (or the value is set to false), the 3ds-web-adapter will call startChallenge(), which inserts an iframe for the challenge window with src set to challengeUrl (Step. 14(C)) in order to show the challenge screen to the card holder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//3ds-web-adapter.js
function startChallenge(url) {

  _callbackFn("onChallengeStart");
  //create the iframe
  $('<iframe src="' + url
      + '" width="100%" height="100%" style="border:0" id="' + "cha_" + iframeId
      + '"></iframe>')
  .appendTo(iframeContainer);

}

Note

If you want to test the challenge scenario, follow the guide here.

You can see that the iframe class has been set to width="100%" and height="100%". This is required because the iframe needs to resize according to the content provided by the ACS. The challenge screen should look similar to the screenshot below:

result screen

The cardholder can now input the authentication data such as an OTP password and submit the challenge form. The ACS will authenticate the transaction and the card holder and return the challenge result.

After the challenge flow is finished, ActiveServer will call the 3ds-notify entry point in the 3DS Requestor backend with event _onAuthResult and then the 3DS Requestor will render the notify_3ds_events.html page, similar to before (Step. 19(C)). The 3ds-web-adapter calls the _onAuthResult() function to get the authentication results and displays them on the process.html page.

Info

In a production environment the ACS will perform complex risk-based authentication from the information obtained about the cardholder. Similarly, the authentication method (e.g. OTP, biometrics etc) will be determined and implemented by the cardholder's issuing bank.

Process 3: Get Authentication Result

After the challenge process is finished (or if the frictionless process results need to be shown in a separate page), we now need to request for the authentication result so that it can be used for the authorisation process.

Why a separate result request is necessary?

You may wonder why you need to request the result again since you already have the result of authentication available after process 2.

This is because the authentication result is returned to the authentication page in Step. 13 by the 3DS Requestor. It is common that the authentication result page is shown as a separate page from the checkout page. In this case, the 3ds-web-adapter could transfer the result back to the 3DS Requestor or the result page, however, it is not a recommended data flow as the authentication result is re-transferred by the client side code and is in general considered as insecure.

The server-side of the 3DS Requestor should always have its own mechanism to get the result back from the origin source: ActiveServer. Instead of transferring the result to the new result page, the 3DS Requestor server-side can provide a result page that shows the authentication result. Therefore, you need to request for a result receipt in this step.

Here in this demo, you can select Show results in separate page >> at the end of process 2 in the process.html page to show the result in a separate page.

To get the authentication result and show it in a separate page, the front-end needs to:

Firstly, in the process.html page, it will store the serverTransId into sessionStorage and go to result.html page.
1
2
3
4
5
6
7
8
9
//process.html
function openResultInNewWindow() {
if (serverTransId) {
  var url = '/result';

  sessionStorage.setItem("serverTransId", serverTransId);
  window.open(url, 'newwindow', 'height=800,width=1000');
}
}

In the result.html page, it call the result() method in 3ds-web-adapter to get the authentication result. The result() method sends a Request for authentication result message to the backend. The backend will receive this request and send a /api/v1/auth/brw/result message to ActiveServer. To check how the back-end handles this request, refer here. The callback function showData() shows the results in the page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//result.html
var serverTransId = sessionStorage.getItem("serverTransId");

//request for authentication result (Step 15(F) or Step 20(C))
result(serverTransId, showData);

//show result in separate page (Step 17(F) or Step 22(C))
function showData(type, data) {
    var toShow = "<dl class='row'>";
    Object.keys(data).forEach(function (k) {
      toShow = toShow + "<dt class='col-sm-4'>" + k + "</dt>" + "<dd class='col-sm-8'>" + data[k]
          + "</dd>";
    });
    toShow += "</dl>";
    $('#showResult').empty().append(toShow);
    $("#resultCard").removeClass("d-none");
}

The new result screen will look like the screenshot below:

result screen

Success

This covers the front-end integration. After authentication is completed the checkout process can proceed with the authorisation process using the Transaction Status, ECI and CAVV to complete the transaction.

Whats next?

Select the next button to learn about Back-end implementation for a 3DS Requestor.