# Handling 3DS Authentication
source: https://developer.mastercard.com/consent-management/documentation/tutorials/card-consents-tutorial/handling-3ds-auth/index.md

In 3DS authentication, two html pages are involved.

### 3DS Fingerprint {#3ds-fingerprint}

First, we need to perform fingerprinting. The tutorial server returns fingerprint.html to the browser:

```html
<html>
    <!-- 
        This page performs the 3DS device fingerprinting (method URL) by opening
        a hidden iframe which POSTS to ACS and the response is a page that collects
        the device information and sends it to ACS. The fingerprint iframe posts
        a message to the window (threeds-method-notification) when it is complete.

        Once fingerprinting is complete we start the authentication.
    -->
    <head>
        <script src="/static/fingerprint.js"></script>
        <script>
            window.onload = function() {
                doFingerprint(
                    '{{ threeDsMethodUrl }}',
                    '{{ threeDSMethodNotificationURL }}',
                    '{{ threeDSMethodData }}',
                    '{{ threeDSServerTransID }}');
            }
        </script>
    </head>
    <body>
        Performing 3DS fingerprinting in hidden iframe. Once that is done we will move 
        on to 3DS challenge.
    </body>
</html>
```

Note: The page shown here is a template from the Python tutorial server. The variables are replaced by the values returned by the `POST /consents`. Java uses a different template engine; therefore, the substitution syntax is different

On load, it calls `doFingerprint` javascript function:

```javascript
//timehout handle
let fingerprintTimeout;

// This listener receives messages posted to the window. When it gets the
// threeds-method-notification message it sends the required data to the
// sserver to start authentication.
function fingerprintCompleteListener(m) {
    if (m.data.type === 'threeds-method-notification') {
        clearTimeout(fingerprintTimeout);
        console.log('fingerprintCompleteListener called');
        proceedAfterFingerprint('complete');
    }
};

// Next step after fingerprinting (either when it is completed or it was not needed)
function proceedAfterFingerprint(fingerprintStatus) {
    // These parameters control what the ACS challenge window will look like. For simplicity we just
    // hard code some
    const params = {
        fingerprintStatus: fingerprintStatus,
        challengeWindowSize: '04', // 600x400
        browserAcceptHeader: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        browserColorDepth: window.screen.colorDepth,
        browserJavaEnabled: true,
        browserLanguage: navigator.language,
        browserScreenHeight: window.screen.height,
        browserScreenWidth: window.screen.width,
        browserTZ: new Date().getTimezoneOffset(),
        browserUserAgent: window.navigator.userAgent,
    }
    post("/start-3ds-authentication", params);
};

// Open fingerprint iframe (hidden). It collects some browser information and posts it to the 3DS ACS
function doFingerprint(threeDsMethodUrl, threeDSMethodNotificationURL, threeDSMethodData, threeDSServerTransID) {
    if (threeDsMethodUrl) {
        const html = `<script>
                document.addEventListener("DOMContentLoaded", function () {
                    var form = document.createElement("form");
                    form.method = "POST";
                    form.action = "${threeDsMethodUrl}";
                    form.appendChild(createInput("threeDSMethodNotificationURL", "${threeDSMethodNotificationURL}"));
                    form.appendChild(createInput("threeDSMethodData", "${threeDSMethodData}"));
                    form.appendChild(createInput("threeDSServerTransID", "${threeDSServerTransID}"));
                    document.body.appendChild(form);
                    form.submit();
                    document.body.removeChild(form);
                });
                function createInput(name, value) {
                    var result = document.createElement("input");
                    result.name = name;
                    result.value = value;
                    return result;
                }
            </script>`

        const iframe = document.createElement("iframe");
        iframe.id = '3ds-fingerprint';
        document.body.appendChild(iframe);
        iframe.style.display = "none";
        const win = iframe.contentWindow;
        if (win != null) {
            const doc = win.document;
            win.name = "3DS Fingerprint";
            doc.open();
            doc.write(html);
            doc.close();
        }
        // Initiate a 10-second timeout to handle fingerprinting delays or failures.
        // This ensures the flow continues if the ACS does not respond
        // or fingerprinting cannot be completed within the expected timeframe.
        fingerprintTimeout = setTimeout(() => {
          proceedAfterFingerprint('timeout');
        }, 10000);
        //add listener for fingerprint complete message
        window.addEventListener("message", fingerprintCompleteListener);
    } else {
        // No threeDsMethodUrl so skip fingerprinting
        proceedAfterFingerprint('unavailable');
    }
};
```

This opens a hidden iframe that posts the 3DS details to the 3DS ACS and waits for it to complete.
Once fingerprinting is done, browser details are posted to the tutorial server `/start-3ds-authentication`.
* Java
* Python

```java
/**
   * This is called once 3DS fingerprinting is done to start the authentication
   *
   * @param body
   * @param model
   * @param redirectAttrs
   * @return String
   */
  @PostMapping("/start-3ds-authentication")
  public String start3dsAuthentication(@RequestParam Map<String, Object> body,
                                       Model model, RedirectAttributes redirectAttrs) {

    StartAuthReq startAuthReq = new StartAuthReq();
    Auth auth = new Auth();
    auth.setParams(body);
    startAuthReq.setAuth(auth);

    try {
      StartAuthResp resp = apiService.getApiClient().startConsentsAuth(cardRef, startAuthReq);

      if (resp.getAuth().getStatus().equals("AUTHENTICATED")) { // frictionless case
        redirectAttrs.addFlashAttribute("authStatus", resp.getAuth().getStatus());
        redirectAttrs.addFlashAttribute("cardRef", cardRef);
        return "redirect:/consents";
      }

      if (resp.getAuth().getStatus().equals("AUTH_FAILED")) { // 3ds failure
        model.addAttribute(ERROR_MSG, resp.toString());
        return ERROR_TEMPLATE;
      }

      if (resp.getAuth().getStatus().equals("AUTH_IN_PROGRESS")) { // proceed to 3ds challenge
        model.addAttribute("params", resp.getAuth().getParams());
        return "threeds-challenge";
      }

    } catch (ApiException e) {
      model.addAttribute(ERROR_MSG, e.getResponseBody());
    } catch (Exception e) {
      model.addAttribute(ERROR_MSG, e.getMessage());
    }

    return ERROR_TEMPLATE;
  }
```

```python
@app.route("/start-3ds-authentication", methods=['POST'])
def start_3ds_authentication():
    """
    This is called when fingerprinting is complete, it will call API to start authentication
    """

    # Call /consents/{cardRef}/start-authentication passing browser screen size etc.
    resp = api_start_authentication(session["card_ref"], session["test_card_details"], request.form)

    # The response contains an updated auth element that looks like:
    #    auth: {
    #        status: 'AUTH_IN_PROGRESS',
    #        type: 'THREEDS',
    #        params: [
    #            'acsUrl': 'xxxx',
    #            'encodedCReq': 'xxxx'
    #        ]
    #    }
    # The params need to be passed to the challenge iframe (see challenge.js)

    params = resp["auth"]["params"]
    status = resp["auth"]["status"]

    if status == 'AUTHENTICATED':  # frictionless case
        print("Frictionless auth")
        session['auth_status'] = status

        return redirect(url_for('consents_info'))

    if status == 'AUTH_FAILED':
        error_msg = resp
        return render_template('error.html', error_msg=error_msg)

    return render_template('threeds-challenge.html', **params)
```

This calls the Consent Management API `POST /consents/{cardRef}/start-authentication`,
passing the browser details (so 3DS ACS can correctly size the challenge window).

The response may contain the ACS URL if a challenge is required. This URL is used to perform the 3DS challenge.

If a challenge is not required, the transaction status will be either `AUTHENTICATED` or `AUTH_FAILED`. Handle both cases accordingly.

### 3DS Challenge {#3ds-challenge}

The html page below will show 3DS challenge iframe:

```html
<html>
    <!-- 
        This page displays the 3DS challenge iframe. Once the challenge iframe is complete 
        it posts a message to the window (threeds-challenge-notification) and calls 
        /verify-authentication API to get the results of the challenge.
    -->
    <head>
        <script src="/static/challenge.js"></script>
        <script>
            window.onload = function() {
                doChallenge('{{ acsUrl }}', '{{ encodedCReq }}');
            }
        </script>
    </head>
    <body>
        Performing 3DS challenge.
    </body>
</html>
```

On page load, it calls `doChallenge` javascript function:

```javascript
// This listener receives messages posted to the window. After listening 
// threeds-challenge-notification message, the challenge results window will pop-up.
function challengeCompleteListener(m) {
    if (m.data.type === 'threeds-challenge-notification') {
        console.log("challengeCompleteListener called");
        window.location = "/verify-authentication";
    }
};

// Opens 3DS challenge iframe and listens to event completion.
function doChallenge(acsUrl, encodedCReq) {

    const html = `<script>
            document.addEventListener("DOMContentLoaded", function () {
                var form = document.createElement("form");
                form.method = "POST";
                form.action = "${acsUrl}";
                form.appendChild(createInput("creq", "${encodedCReq}"));
                document.body.appendChild(form);
                form.submit();
                document.body.removeChild(form);
            });
            function createInput(name, value) {
                var result = document.createElement("input");
                result.name = name;
                result.value = value;
                return result;
            }
        </script>`

    const iframe = document.createElement("iframe");
    iframe.id = "3ds-challenge";
    iframe.width = "600px";
    iframe.height = "400px";
    iframe.frameBorder = "0";
    iframe.style.display = 'block';
    iframe.style.position = 'absolute';
    iframe.style.top = "100px";
    iframe.style.left = "50%";
    iframe.style.transform = "translate(-50%, 0%)";
    iframe.style.background = "white";
    document.body.appendChild(iframe);
    const win = iframe.contentWindow;

    if (win != null) {
        const doc = win.document;
        win.name = "3DS Challenge";
        doc.open();
        doc.write(html);
        doc.close();
    }

    window.addEventListener("message", challengeCompleteListener);
};
```

This displays the 3DS challenge iframe and waits for it to be complete.

Once the challenge is complete, it gets the tutorial page `/verify-authentication`.

Please see the [Verify Authentication](https://developer.mastercard.com/consent-management/documentation/tutorials/card-consents-tutorial/verify-auth/index.md) section to see how authentication is completed.
