# Handling 3DS Authentication
source: https://developer.mastercard.com/consent-management/documentation/tutorials/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
// This listener receives events from the window.
// On the arrival of threeds-method-notification event, it forwards 
// the required data to the server to start authentication.
function fingerprintCompleteListener(m) {
    if (m.data.type === 'threeds-method-notification') {
        console.log('fingerprintCompleteListener called');
        proceedAfterFingerprint('complete');
    }
};

// Next step after fingerprinting (either when it is completed or it was not needed)
function proceedAfterFingerprint(fingerprintStatus) {
    // Below mentioned parameters controls the looks of ACS challenge window. Values differs as per device configuration.
    // For simplicity we have hardcoded some of them.
    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);
};

// Opens a hidden fingerprint iframe to collect the browser specific information and forwards 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();
        }
        window.addEventListener("message", fingerprintCompleteListener);
    } else {
        // Fingerprinting skipped due to absence of threeDsMethodUrl
        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
 * @return String
 * @throws ApiException
 */
@PostMapping("/start-3ds-authentication")
public String start3dsAuthentication(
    @RequestParam Map<String, Object> body, Model model) throws ApiException {

    StartAuthReq startAuthReq = new StartAuthReq();
    startAuthReq.setCardDetails(cardDetails);
    Auth auth = new Auth();
    auth.setParams(body);
    startAuthReq.setAuth(auth);
    StartAuthResp resp = apiService.startAuth(cardRef, startAuthReq);

    //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)
    //
    //Note. If authentication is frictionless, we will reeive auth.status= AUTHENTICATED 
    //and we will be finished at this point, 
    //but for our test card, we need to show the challenge window

    model.addAttribute("params", resp.getAuth().getParams());
    return "threeds-challenge";
}
```

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

    # Call /consents/{cardRef}/start-authentication passing browser screen size etc.
    resp = callStartAuthentication(cardRef, testCardDetails, 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)

    # Note. If authentication is frictionless we will be finished at this point, but for our test
    # card we need to show the challenge window
    params = resp["auth"]["params"]
    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 contains the ACS URL, which is the URL of the challenge window.

### 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/consents-tutorial/verify-auth/index.md) section to see how authentication is completed.
