# SDK + PingFederate Integration Guide

## SDK + PingFederate Integration Guide

### Introduction

Use this guide to add Twosense MFA to your PingFederate SSO authentication flow.

### Requirements

* For Twosense to work properly, service providers must be configured to receive SAML responses as POST, not GET.

### Add Twosense Assets

Your Twosense contact will provide an integration package as a zip file. In this package there is a directory named `template`. Copy the **contents** of this directory to the `/server/default/conf/template` directory of your PingFederate installation.

After copying, the directory structure should look like this:

```
📁 server/
└── 📁 default/
    └── 📁 conf/
        └── 📁 template/
            ├── 📄 html.form.twosense.mfa.embedded.html
            |-- 📄 html.form.twosense.session.embedded.html
            ├── 📄 html.form.twosense.challenge.html
            └── 📁 assets/
                └── 📁 scripts/
                    ├── 📄 twosense-sw.js
                    └── 📄 twosense-client.js
```

### Install PingFederate Adapter

The integration package will also contain a file named `pf-twosense-adapter.jar`. Copy this file to your PingFederate installation's `/server/default/deploy/` directory, then restart PingFederate to load the adapter.

### Allow Twosense Resources

You will need to make some additional changes to your Ping configuration to allow the Twosense-related resources to load properly.

#### Allow Service Worker

The Twosense integration utilizes a service worker to perform MFA. To ensure it works properly, you must add the following lines to your PingFederate config's `/server/default/data/config-store/response-header-runtime-config.xml` file, inside of the `<con:config>` element:

```xml
<!-- Add inside the <con:config> element -->
<con:map name="Service-Worker-Allowed">
    <con:item name="value">/</con:item>
    <con:item name="include-patterns">*.js</con:item>
</con:map>
```

Once this change is made, the Ping service must be restarted for it to take effect.

#### Update Content Security Policy

If you have a custom content security policy, ensure that these directives are allowed:

* `img-src data:`
* `style-src 'self' 'unsafe-inline' *.googleapis.com`
* `worker-src 'self'`
* `connect-src 'self' http://127.0.0.1:27367 *.twosense.ai *.mixpanel.com`
* `script-src 'self' 'unsafe-inline' data:`
* `child-src 'self' data:`
* `font-src 'self' fonts.gstatic.com`

For example, here is a policy which will allow all the resources necessary for the Twosense SDK to work:

{% code overflow="wrap" %}

```
default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline' *.googleapis.com; script-src 'self' 'unsafe-inline' data:; child-src 'self' data:; connect-src 'self' http://127.0.0.1:27367 *.twosense.ai *.mixpanel.com; font-src 'self' fonts.gstatic.com
```

{% endcode %}

Note that your CSP may look different based on your situation; this is a minimal example.

If you are experiencing any additional CSP-related errors after these changes, please reach out to Twosense support.

#### Allow Local Network Access

{% hint style="warning" %}
If you use other Chromium-based browsers (such as Microsoft Edge) in your organization, you will need to repeat these steps for those browsers.
{% endhint %}

The Twosense SDK uses local requests to communicate with the Windows agent, and starting with Chrome version 142 this is automatically blocked unless explicitly allowed by the user or via Group Policy. In order to allow this behavior, you will need to add a policy exception for your PingFederate domain.

Use the [`LocalNetworkAccessAllowedForUrls`](https://chromeenterprise.google/policies/#LocalNetworkAccessAllowedForUrls) policy to grant the Twosense SDK service worker local network access. For example, if your PingFederate instance is hosted at `https://auth.example.com:9031`, you would add the following to the list of allowed URLs:

```
https://auth.example.com:9031/assets/scripts/twosense-sw.js
```

## Twosense MFA Adapter Configuration

### Create Twosense IdP Adapter

1. Navigate to **AUTHENTICATION** and select **IdP Adapters**.
2. Click **Create New Instance**.
3. In the **Type** tab, set the following:
   * *Instance Name*: **Twosense MFA**
   * *Instance ID*: **TwosenseMfaForm**
   * *Type*: **Twosense IdP Adapter**
   * Click **Next**.
4. In the fields labeled **Twosense IKEY** and **Twosense SKEY**, enter the values provided by your Twosense contact. If you do not have these values, please reach out to Twosense support.
5. If you require additional configuration options, such as proxy settings, press **Show Advanced Fields** and configure as needed.
6. Click **Next**.
7. Press **Test Connection** to verify connectivity to the Twosense service, then click **Next**.
8. In the **Extended Contract** tab, click **Next**.
9. In the **Adapter Attributes** tab, check the **Pseudonym** checkbox for the **result** attribute, and click **Next**.
10. In the **Adapter Contract Mapping** tab, click **Next**.
11. Review the **Summary** tab, and click **Save**.

### Modify the primary authentication adapter to return the userSid

The Twosense MFA adapter requires the user's `objectSid` attribute. In order to do this, we need to extend the contract of the primary login form IdP adapter.

1. Navigate to **AUTHENTICATION** and select **IdP Adapters**.
2. Click on the IdP adapter of your primary login HTML form.
3. In the **Summary** tab, click **Extend Contract**.
   1. Under **Extend the Contract**, add the **objectSid** attribute.
   2. Navigate back to the **Summary** tab.
4. In the **Summary** tab, click **Attribute Sources & User Lookup**.
   1. In the **Attribute Source & User Lookup** tab, click **Add Attribute Source**.
      1. In the **Data Store** tab, set the following:
         * *ATTRIBUTE SOURCE ID*: **ActiveDirectory** (or another descriptive name)
         * *ATTRIBUTE SOURCE DESCRIPTION*: **Active Directory** (or another descriptive name)
         * *ACTIVE DATA STORE*: **{Your Active Directory Datastore}**
         * Click **Next**.
      2. In the **LDAP Directory Search** tab, set the following:
         * *BASE DN*: **{Your Active Directory Search Base DN}**
         * *Attributes to return from search*: **objectSid**
         * Click **Next**.
      3. In the **LDAP Binary Attribute Encoding Types** tab, select **SID** as the *Attribute Encoding Type* for *objectSID*, and click **Next**.
      4. In the **LDAP Filter** tab, set **FILTER** to **sAMAccountName=${username}**, click **Next**.
      5. In the **Summary** tab, click **Done**.
   2. In the **Attribute Source & User Lookup** tab, click **Next**.
   3. In the **Adapter Contract Fulfillment** tab, for **objectSid**:
      * Select **LDAP (Active Directory)** as the *Source*.
      * Select **objectSid** as the *Value*.
      * Click **Next**.
   4. In the **Issuance Criteria** tab, click **Next**.
   5. In the **Summary** tab, click **Done**.
5. In the **Adapter Contract Mapping** tab, click **Save**.

### Embed Twosense Client in the primary authentication HTML template

The Twosense SDK detects the presence of the primary login form by looking for a specific attribute in the HTML form. In order for this detection to work, we need to modify the primary login form.

1. Navigate to **AUTHENTICATION** and select **IdP Adapters**.
2. Click on the IdP adapter of your primary login HTML form.
3. Find the name of the HTML form file by looking for the **Login Template** field in the **Summary** tab. The default value is `html.form.login.template.html`.
4. Find the file on your PingFederate instance under `pingfederate/server/default/conf/template/`.
5. Modify the HTML form to include the following attribute in the `<form>` tag `data-twosense-id="primary-login-form"`. The following is an example of a modified HTML form:

{% code overflow="wrap" %}

```html
<form method="post" action="$url" autocomplete="off" data-twosense-id="primary-login-form">
```

{% endcode %}

6. At the bottom of the template, directly before the closing `</body>` tag, add the following lines:

{% code overflow="wrap" %}

```html
<script
  src="/assets/scripts/twosense-client.js"
  data-sw-path="/assets/scripts/twosense-sw.js">
</script>
```

{% endcode %}

{% hint style="warning" %}
You will need to embed the Twosense client script on each primary login form template where you want Twosense to be active.

Please note that the location of this snippet matters; it **must** be added before the closing `</body>` tag, not in `<head>` or elsewhere.
{% endhint %}

### Create Authentication Policy

You can create a new authentication policy, or modify an existing one. These instructions will assume you are creating a new policy.

1. Navigate to **AUTHENTICATION** and select **Policies**.
2. Click **Add Policy**.
3. Set **Name** to **Twosense MFA Policy** or another descriptive name.
4. Under **Policy**, select any SP connection Selector you wish to use.
5. Under **NO**, click **Continue**.
6. Under **YES**, select your ***primary login HTML form IdP adapter***.
   1. Under **FAIL**, select **DONE**.
   2. Under **SUCCESS**, select ***Twosense MFA***.
      * Click **Options**, and in the **Incoming User ID** form:
        * Set **Attribute** to **objectSid**.
        * Check **User ID Authenticated**.
        * Click **Done**.
   3. Under **SUCCESS** for the Twosense and manual MFA IdP adapters, select the appropriate policy contract.
   4. Under **FAIL**, select the action you wish to occur when communication between PingFederate and Twosense fails.
   5. Configure the **Rules** for the **Twosense MFA** adapter according to the table in the section below.
7. Click **Done**.
8. In the **Policies** tab, click **Save**.

#### Twosense MFA Adapter Rules

The Twosense adapter will set the `policy.action` attribute to different values for different outcomes. The following table lists the possible values and their meaning. Use these values to create rules to fit your organization's needs.

| **`policy.action` Value** | **Outcome**                                                                                                          |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `no-agent`                | The Twosense agent was not detected on the client machine.                                                           |
| `twosense-client-error`   | An internal error occurred in the Twosense SDK.                                                                      |
| `challenge`               | The user was not authenticated (i.e., due to a low trust score) and should be presented an additional MFA challenge. |

For example, here's how you can customize your policy with rules based on the outcome:

![](https://3598673384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7E6grtzdmEvUIic70zfp%2Fuploads%2Fgit-blob-77cdd0e24c04709d6d39b600b975c26fea8bf51f%2Frules-example.png?alt=media)

Then you can use these results to determine the next steps in your policy:

![](https://3598673384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7E6grtzdmEvUIic70zfp%2Fuploads%2Fgit-blob-8bbcb2993b3d13c175b0dc9a4f94e7d7c8caf6e5%2Frules-branches.png?alt=media)

## Twosense Session Adapter Configuration

If your setup requires information about the authenticating user's current Twosense session, you can set up the Twosense Session Adapter to retrieve this information and use it in your policy.

### Create Twosense Data Store

1. Navigate to **SYSTEM** and select **Data Stores**.
2. Click **Add New Data Store**.
3. In the **Data Store Type** tab, set the following:
   * *Name*: **Twosense API**
   * *Type*: **REST API**
   * Click **Next**.
4. In the **Configure Data Store Instance** tab, perform the following:
   1. Add a new row to "Base URLs and Tags".
      * *Base URL*: `https://webapi.twosense.ai`
      * Click **Update**.
   2. Add the following rows to "Attributes".
      * `startTime` -> `/startTime`
      * `userId` -> `/userId`
      * `device` -> `/device`
      * `ipAddress` -> `/ipAddress`
   3. Set **Authentication Method** to "OAuth 2.0 Bearer Token".
   4. Set **OAuth Token Endpoint** to `https://webapi.twosense.ai/oauth/token`.
   5. Set **Client ID** and **Client Secret** to the values provided by Twosense.

### Create Twosense Session Adapter

1. Navigate to **AUTHENTICATION** and select **IdP Adapters**.
2. Click **Create New Instance**.
3. In the **Type** tab, set the following:
   * *Instance Name*: **Twosense Session Form**
   * *Instance ID*: **TwosenseSessionForm**
   * *Type*: **HTML Form IdP Adapter**
   * Click **Next**.
4. In the **IdP Adapter** tab, under **Password Credential Validator Instance**, click **Add a new row to 'Credential Validators'**:
   * Select **Twosense MFA RADIUS PCV**
   * Click **Update**.
   * Set the following fields:
     * *Challenge Retries*: **1**
     * Click **Show Advanced Fields**.
     * *Login Template*: **html.form.twosense.session.embedded.html**
     * *Allow Username Edits During Chaining*: **true**
     * Click **Next**.
5. In the **Extended Contract** tab, extend the contract with the following fields as needed per your requirements:
   * `sessionStartTime`
   * `sessionUserId`
   * `sessionDevice`
   * `sessionIpAddress`
   * Click **Next**.
6. In the **Adapter Attributes** tab, check the **Pseudonym** checkbox for the **username** attribute, and click **Next**.
7. In the **Adapter Contract Mapping** tab, click **Configure Adapter Contract**.
   1. In the **Attribute Sources & User Lookup** tab, click **Add Attribute Source**.
      1. In the **Data Store** tab, set the following:
         * *ATTRIBUTE SOURCE ID*: **TwosenseAPI**
         * *ATTRIBUTE SOURCE DESCRIPTION*: **Twosense API**
         * *ACTIVE DATA STORE*: **Twosense API**
         * Click **Next**.
      2. In the **Configure Data Source Filters** tab, set the following:
         * *RESOURCE PATH*: `/sessions/${policy.action}`
         * Click **Next**.
   2. In the **Adapter Contract Fulfillment** tab, set the **Source** field to "Other (Twosense API)" for each of the extended contract attributes, then set the **Value** field to the corresponding attribute name.
   3. In the **Issuance Criteria** tab, click **Next**.
   4. In the **Summary** tab, click **Done**.
   5. Click **Next**.
8. Review the **Summary** tab, and click **Save**.

#### Usage in Policies

You can use this adapter anywhere in your policy where you need to retrieve the current user's Twosense session information.

To use the adapter, add it to your policy and ensure the `userSid` is being passed as the user ID in **Options** → **Incoming User ID**.

Create a rule on the adapter which checks that the session attribute of interest (e.g., `sessionUserId`) is not empty (set "Value" to a single space, since Ping does not allow you to set it to an empty string), and make sure "Default to Success" is unchecked.

![](https://3598673384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7E6grtzdmEvUIic70zfp%2Fuploads%2Fgit-blob-0e9f010c42a61d5ec129e20a49ea92bf6d3fe237%2Fsession-rules.png?alt=media)

Using the example above, we end up with two branches from the adapter: "Fail" and "Session".

![](https://3598673384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7E6grtzdmEvUIic70zfp%2Fuploads%2Fgit-blob-b4d2c7cd94d6ccdaccc70b511e963707864e68b6%2Fsession-branches.png?alt=media)

The "Fail" branch will be triggered if the user's computer does not have Twosense, or if there is no session for the authenticating user. The "Session" branch will be triggered if the session is found, and you will be able to use the session attributes further in your policy.
