Securing Drift on Your Site with an iframe

Some sites may have security restrictions that go beyond what a CSP is able to provide in terms of sandboxing. If that's the case for your site, you likely have a policy that all third-party Javascripts must run within an iframe attached to a separate domain. With a little bit of extra Javascript the Drift widget fully supports running in this type of environment.

General Setup

To run Drift inside of another iframe on your site, you'll need two separate scripts - one that runs on the parent page (your actual site) and one that runs inside an iframe of your choosing.

📘

The importance of using a separate domain

iFrames are a good way of isolating your website from 3rd party code, but only if you have loaded that 3rd party code from a different domain than your top level domain.

For example if your top level domain is example.com, and the Drift JavaScript is hosted on cdn-example.com, then proper cross-origin browser security will prevent malicious code from spreading across those domains.

642

Outside the Iframe

Because the Drift widget is a rich, interactive user experience on your website, it requires some additional code to resize and reposition the widget's iframe element as the user interacts. This ensures that the widget is never visually cut off, and more importantly, doesn't prevent the user from interacting with other elements on your site.

This also allows your website to pass important context (like the URL of the current page) that is used for targeting. It's entirely up to you how little or how much context you want to pass into the iframe. For example if your URLs contain sensitive user information you may want to sanitize them.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://mydomain.com/drift-snippet.js"></script>
  </head>
  <body>
    <iframe id="drift-iframe" style="height:0;width:0;position:absolute;border:none;" src="https://mycdndomain.com/drift-frame.html"/>
  </body>
</html>
/** Snippet for the parent page to properly resize the iframe and pass context */

// create dummy page context for analytics / targeting
// you can omit / clean data any sensitive data from these values
const generateNewContext = ()=>  {
  return {
    window: {
      location: {
        hash: window.location.hash,
        host: window.location.host,
        hostname: window.location.hostname,
        href: window.location.href,
        origin: window.location.origin,
        pathname: window.location.pathname,
        port: window.location.port,
        protocol: window.location.protocol,
        search: window.location.search
      },
      navigator: {
          language: window.navigator.language,
        browserLanguage: window.navigator.browserLanguage,
        userAgent: window.navigator.userAgent
      },
      innerHeight: window.innerHeight,
      innerWidth: window.innerWidth
    },
    document: {
      title: document.title,
      referrer: document.referrer
    }
  }
}

window.addEventListener("resize", () => {
  const iframe = document.getElementById('drift-iframe')
  iframe.contentWindow.postMessage({ type: 'driftUpdateContext', data: generateNewContext() }, '*')
})

window.addEventListener('scroll', (event) => {
  const iframe = document.getElementById('drift-iframe')
  iframe.contentWindow.postMessage({type: 'driftParentScroll', data:{scroll:true}, target:'drift.parentScroll'},'*')
})

window.addEventListener('message', function (event) {
  var iframe = document.getElementById('drift-iframe')
  if  (!(iframe && iframe.contentWindow) && event.source === iframe.contentWindow) {
    return
  }

  // on startup - pass created context into iframe
  var message = event.data
  if (message.type === 'driftIframeReady') {
    iframe.contentWindow.postMessage({ type: 'driftSetContext', data: generateNewContext() }, '*')
  }

  // on widget size change - apply new size / positioning to iframe
  if (message.type === 'driftIframeResize') {
    var styles = message.data.styles
    for (var key in styles) {
      if (!styles.hasOwnProperty(key)) {
        continue
      }
      iframe.style.setProperty(key,  styles[key])
    }
  }
})

Inside the Iframe

Inside the iframe you must add listeners and callbacks for the outside-the-iframe snippet above, as well as for the Widget API lifecycle events. This snippet will act as a proxy between your site and the Drift Widget API. You can add additional methods here depending on the types of events the Drift Widget needs to expose to other integrations (e.g. if you'd like to send an event to Google Analytics every time a chat is started).

<!DOCTYPE html>
<html>
<head>
  <script async="true" crossorigin="anonymous" src="https://js.driftt.com/conductor"></script>
  <script src="https://mycdndomain.com/drift-frame-snippet.js"></script>
</head>
<body>
</body>
</html>
/** Snippet for the iframe. Initializes the inner Drift iframe and acts as a communcation layer for the parent page. */
window.drift=window.drift||function(){(drift.q=drift.q||[]).push(arguments)};

// rebroadcast drift widget API events to parent page
drift('on', 'iframeResize', function (data) {
  window.parent.postMessage({ type: 'driftIframeResize', data }, '*')
})

window.addEventListener('message', function (event) {
  if (event.source !== window.parent) {
    return
  }

  var message = event.data

  if (message && message.type === 'driftUpdateContext') {
    drift('setContext', message.data)
  }

  // set initial context, put widget in "iframeMode", load widget
  if (message && message.type === 'driftSetContext') {
    drift('setContext', message.data);
    drift('config', {
      iframeMode: true,
      iframeSandbox: 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms'
    })
    drift('page')
    drift('init','xxxxxxxxx') // Your Drift embed ID goes in the second argument
  }

})

// indicate iframe is ready to receive context
window.parent.postMessage({ type: 'driftIframeReady' }, '*')

Required Attributes

You can add sandbox attributes to the Drift widget's iframe to further sandbox the iframe from your website.

🚧

iFrame sandbox attributes are an addition to securing Drift on your website, but not nearly as important as loading the top layer iFrame from a separate domain as your main website domain.

Web browser's cross-origin restrictions are key to preventing 3rd party code from accessing or altering your website's content.

The following sandbox attributes are required for the Drift widget to function correctly:

  • allow-scripts - Allows the Drift widget to execute Javascript within the iframe.
  • allow-same-origin - Allows the Drift widget iframe to run with the correct origin (js.driftt.com).
  • allow-popups - Hyperlinks in chat messages have target="_blank" to allow them to open in a new tab rather than navigating the user within the iframe. Without this flag those links are silently blocked.
    - allow-popups-to-escape-sandbox - Allows linked pages to function correctly. Without this flag sites you link to are subject to the sandbox parameters and may not render correctly.
  • allow-forms - The Drift widget uses forms in a handful of places to collect things like email addresses within the widget in an accessibility friendly way.

You can specify the iframe's sandbox attributes with the following Widget API call.

drift('config', {
  iframeSandbox: 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms'
})