Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prebid.js, UnifiedID, and other user IDs #3046

Closed
bretg opened this issue Sep 4, 2018 · 24 comments
Closed

Prebid.js, UnifiedID, and other user IDs #3046

bretg opened this issue Sep 4, 2018 · 24 comments

Comments

@bretg
Copy link
Collaborator

bretg commented Sep 4, 2018

Overview

UnifiedID is a way of establishing 'universal' user device IDs for web browsers instead of having dozens of exchanges sync IDs with hundreds of demand sources. The basic concept is that the UnifiedID organization stores a set of global IDs at several locations and each publisher using Prebid.js should be able to take advantage of UnifiedID to get better bids from supporting DSPs.

Note that UnifiedID is not a single ID, but potentially a set of IDs. At first just from The TradeDesk, but someday perhaps others.

There are several main steps in the process:

  1. Javascript in the browser looks in local storage (cookie or HTML storage) for the set of IDs. If it can't find them locally, it connects to the ID server to retrieve the values for this user, then stores them locally.
  2. Any ad requests in the page should send the IDs to all adapters. Those that support IDs pass the values to their servers.
  3. Supporting SSPs send the IDs to DSPs that support the ID

Note that universal user IDs aren't needed in the mobile app world because device ID is available in those ad serving scenarios.

Prebid, Unified ID, and other User IDs

Prebid.org intends to support integration of Unified ID and other 'universal' IDs as a core feature in header bidding products with appropriate publisher-level controls. We will also deprecate the "pubCommonId", folding it's functionality into this more generic module. (The existing module will be left for a few months as there will be page changes to make.)

Prebid support of IDs should include:

  • Generic support for user ID systems
  • Opt-in to supporting one or more of the user ID schemes, which should be a PBJS module.
  • Publishers define which user ID system and server they wish to use
  • Publishers define the storage of the user ID in their first party domain cookie space, conforming to GDPR rules. Each user ID schema should support its own cookie, which allows for them to have different storage schemas and expiration times.
  • For sites that don't support GDPR, the module should also support a cookie-based opt-out like the PubCommonID module does. Specifically, if there exists a first party cookie called "_pbjs_id_optout" (with any value), then this module becomes inactive for this particular user just like if there was a CMP without Purpose 1 consent.
  • Publishers specify when in the page the user ID synchronization should occur. This allows the publisher to minimize user impact by shifting the ID request to later in the process.
  • When the User ID module is in place, the IDs should be placed in every bidRequest in the userIds object. e.g. bidRequest.userIds={"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3", "appnexus_id": "1234", "pubcid": "c4a4c843-2368-4b5e-b3b1-6ee4702b9ad6", digitrustId: {"id": "skloi903409as9kf8cj", "keyv": 4}}
  • Note that the value of the key from each system may be an object
  • The first two systems supported are Unified ID and pubCommonId, but it must be possible to extend the system to support other user ID systems
  • The length of time the ID values are stored locally should be configurable, but default per ID scheme. Default for Unified ID is 30 days. Default for PubCommonID is 1 year.
  • The response from each ID system should be able to include a time-to-live setting that would override the page-defined value.
  • Unified ID partner code defaults to "prebid" and url defaults to "http://match.adsrvr.org/track/id?ttd_pid=PARTNER&fmt=json".
  • Some types of ID systems may not be subject to the sync delay. e.g. PubCommonID generates an ID locally.
  • Each submodule should be able to access the gdprConsent settings for the user

The role of Prebid.js is to:

  • Collect publisher configuration
  • Invoke the new User ID module when config is present
    • If the relevant local storage is present, the module parses the scheme-dependent format and injects the resulting ID into bidRequest.userIds
  • bidRequest.userIds is made available to Prebid.js adapters
  • bidRequest.userIds is made available to Prebid Server S2S adapters
  • Document the feature, benefits, and implementation

The role of Prebid Server is to:

  • Pass IDs through to the server-side adapters

Then it's up to each adapter to read the ID values from a standard location and forward it through their pipeline.

Use Cases

Examples of the proposed publisher configuration for various scenarios follows:

  1. Publisher supports Unified ID and first party domain cookie storage
pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "unifiedId",
            params: {
                partner: "PARTNER_CODE",
                url: "URL_TO_UNIFIED_ID_SERVER"
            },
            storage: {
                type: "cookie",  
                name: "pbjs-unifiedid",       // create a cookie with this name
                expires: 60                        // cookie can last for 60 days
            }
        }],
        syncDelay: 5000       // 5 seconds after the first bidRequest()
    }
});
  1. Publisher supports UnifiedID with HTML5 local storage, synchronously with the first PBJS
pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "unifiedId",
            params: {
                partner: "PARTNER_CODE",
                url: "URL_TO_UNIFIED_ID_SERVER"
            },
            storage: {
                type: "html5",
                name: "pbjs-unifiedid"    // set localstorage with this name
            },
            maxDelayToAuction: 500 // implies syncDelay of 0
                           // wait up to 500ms before starting auction
        }]
    }
});
  1. Publisher has integrated with unifiedID on their own and wants to pass the unifiedID directly through to Prebid.js
pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "unifiedId",
            value: {"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3", 
                     "appnexus_id": "1234"}
        }]
    }
});
  1. Publisher supports PubCommonID and first party domain cookie storage
pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "pubCommonId",
            storage: {
                type: "cookie",  
                name: "_pubCommonId",       // create a cookie with this name
                expires: 1825                           // expires in 5 years
            }
        }]
    }
});
  1. Publisher supports both unifiedID and PubCommonID and first party domain cookie storage
pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "unifiedId",
            params: {
                partner: "PARTNER_CODE",
                url: "URL_TO_UNIFIED_ID_SERVER"
            },
            storage: {
                type: "cookie",  
                name: "pbjs-unifiedid"       // create a cookie with this name
            }
        },{
            name: "pubCommonId",
            storage: {
                type: "cookie",  
                name: "pbjs-pubCommonId"       // create a cookie with this name
            }
        }],
        syncDelay: 5000       // 5 seconds after the first bidRequest()
    }
});
  1. DigiTrust example
pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "digitrust",
            params: {
                memberId: "123abc"
            },
            storage: {
                type: "cookie",  
                name: "pbjs-digitrust"       // create a cookie with this name
            }
        }],
        syncDelay: 5000       // 5 seconds after the first bidRequest()
    }
});

Design Notes

Prebid.js will use the setConfig values configured by the publisher to look for locally stored ID values. If it doesn't find local IDs, it will reach out to the configured URLs, storing any results for the specified number of days as appropriate after checking for GDPR consent.

The prebidServerBidAdapter adds the values to the user.ext.eids section of the OpenRtb2 protocol:

{
    "user": {
        "ext": {
            "eids": [{
                "source": "adserver.org",
                "uids": [{
                    "id": "111111111111",
                    "ext": {
                        "rtiPartner": "TDID"
                    }
                }]
            },
            {
                "source": "pubcommon",
                "uids": [{
                    "id":"11111111"
                }]
            }
            ],
            "digitrust": {
                "id": "11111111111",
                "keyv": 4
            }
        }
    }
}```


Suggested Pseudo-Code
1. The User ID Module (UIM) looks for its config. If no config, exit. Nothing to do.
1. UIM should check the status of the GDPR and exit if the user doesn't consent to local storage of data. Specifically, if the PBJS consentManagement module is present, then the code needs to assume the user does not consent unless it sees a consent string that specifically allows Purpose 1 (store local state). If consent is required but not found, the module should just exit after logging a debug warning for when pbjs_debug=true.
1. For each userId specified in the config, check the appropriate local storage to see if we already have the ID JSON
1. If we don't have local storage for any of the specified userIds, set up a timer to initiate the call the appropriate sub-module 'getId function'. This function has access to the gdprConsent info if available. The responses should cause the UIM to store the JSON return data into the appropriate local storage.
1. If we do have local storage for a sub-module, call its 'decoding function' and build up the object that will be sent to the adapters. e.g. bidRequest.userIds={"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3", "appnexus_id": "1234", "pubcid": "c4a4c843-2368-4b5e-b3b1-6ee4702b9ad6", digitrustId: {"id": "skloi903409as9kf8cj", "keyv": 4}}
1. As an example implementation, modify the Rubicon PBJS adapter to read 'bidRequest.userIds.tdid' and pass it through to the exchange.

Each of the "sub-modules" in the User ID Module has these interfaces:
- A get-ID function. This ID-specific function will be called when it's time to retrieve the ID from that service. It will be passed the `params` object from config. The response is assumed to return JSON that will be directly stored by the module in the appropriate local storage with the appropriate expiration date. Note that expiration may be specified in either the `expires` field of the publisher config, OR overridden by the `expires` field on the JSON response from the ID service.
- A JSON decoding function. This ID-specific function will be called when the module is compiling the object that will be passed to all of the adapters. It takes the full data stored in local state for the sub-module and returns the string or object that should be added to the adapter-visible ID object. In most cases, 

<a name="spec"></a>
## Spec

### Implementation

module

test if local storage/cookies are enabled
* if neither is enabled, exit
* else add enabled types to enabledStorageTypes

test if any user ids are set in configuration
* if none exist, exit

iterate sub-modules, for each submodule
1. check if configuration exists with matching sub-module config name: 
  * skip sub-module if none exists
  
2. validate sub-module config storage props and params
  * syncDelay (optional)
  
  * storage (required)
    * type (required)
    * name (required)
    * expires (required)
    
  * params (optional)
    * partnerCode
    * url
  
3. if syncDelay exists, use setTimeout with callback wrapping next function, else call immediately
   
4. use storage key and type to retrieve stored value
  * if stored value exists, add value to data array for adding to bid request
  * else, call sub-module getId method to retrieve
    

/**

  • @callback webserviceCallback
  • @param {Object} response - assumed to be a json object
    */

/**

  • @callback getId
  • @summary submodule interface for getId function
  • @param {Object} data
  • @param {webserviceCallback} callback - optional callback to execute on id retrieval
    */

/**

/**

/**

  • PubCommonID Module
  • @type {IdSubmodule}
    */
    const pubCommonId = {
    configKey: 'pubcid', expires: 2628000, decode: function(idData) {
    return {
    'ext.pubcommonid': idData
    }
    }, getId: function(data, callback) {
    if (data.params.url) {
    ajax(data.params.url, function(response) {
    callback(response)
    })
    } else {
    // log error, missing required param
    }
    }
    }

/**

  • User ID Module
  • @type {IdSubmodule}
    */
    const unifiedId = {
    configKey: 'unifiedid', expires: 20000, decode: function(idData) {
    return {
    'ext.unifiedid': idData
    }
    }, getId: function(url, syncDelay, callback) {
    callback('response data')
    }
    }

/**

  • ID data for appending to bid requests from the requestBidHook
  • @type {Array.}
    */
    const extendedBidRequestData = []

    /**

    • Decorate ad units with user id properties. This hook function is called before the

    • real pbjs.requestBids is invoked, and can modify its parameter

    • @param {PrebidConfig} config

    • @param next

    • @returns {*}
      */
      function requestBidHook (config, next) {
      // Note: calling next() allows Prebid to continue processing an auction, if not called, the auction will be stalled.
      // pass id data to adapters if bidRequestData list is not empty
      if (extendedBidRequestData.length) {
      const data = extendedBidRequestData.reduce(function(aggregate, item) {
      Object.keys(item).forEach(function(propName) {
      aggregate[propName] = item[propName]
      return aggregate
      })
      }, {})

       // if data exists, append to bid objects, which will be incorporated into bid requests
       if (Object.keys(data).length) {
           const adUnits = config.adUnits || $$PREBID_GLOBAL$$.adUnits
           if (adUnits) {
               adUnits.forEach((adUnit) => {
                   adUnit.bids.forEach((bid) => {
                       Object.assign(bid, data)
                   })
           })
           }
       }
      

      }
      return next.apply(this, arguments)
      }

    /**

    • Helper to check if local storage or cookies are enabled
    • Question: Should we add the local storage methods to utils?
    • @returns {boolean|*}
      */
      function localStorageEnabled () {
      return typeof localStorage === 'object' || utils.cookiesAreEnabled()
      }

    /**

    • Helper to set key va
    • lue to from local storage or cookies as fallback
    • Question: Should we add the local storage methods to utils?
    • @param {Object|string|number} data
    • @param {string} name
    • @param {string} storageType - either 'cookie' or 'localStorage'
    • @param {number} expires
    • @returns {boolean}
      */
      function saveLocalStorageValue (data, name, storageType, expires) {
      try {
      if (storageType === 'localStorage' && localStorage) {
      // since local storage does not have expiration built in, it will need to be emulated by adding a property
      // named expiration and when reading from local storage it will need to be compared against the current date.
      // if the current date is past the expiration, the local storage key should be deleted
      localStorage.setItem(name, data)
      } else {
      // we need to discuss if we want to automatically fallback to cookie if local storage is not available
      setCookie(name, data, expires)
      }
      } catch (e) {
      // log error saving to local storage
      return
      }
      return true
      }

    /**

    • @param name
    • @param type
    • @returns {*}
      */
      function getLocalStorageValue (name, type) {
      try {
      if (type === 'localStorage' && localStorage) {
      return localStorage.getItem(name)
      } else {
      return getCookie(name)
      }
      } catch (e) {
      // log error
      }
      }

    /**

    • Helper to set key value in cookies
    • Question: Should we add the cookie methods to utils?
    • @param {string} name
    • @param {string|number} value
    • @param {number} expires
      */
      function setCookie (name, value, expires) {
      const expTime = new Date()
      expTime.setTime(expTime.getTime() + expires * 1000 * 60)
      window.document.cookie = name + '=' + encodeURIComponent(value) + ';path=/;expires=' + expTime.toGMTString()
      }

    /**

    • Helper get value for key from cookies
    • Question: Should we add the cookie methods to utils?
    • @param {string} name
    • @returns {}
      /
      function getCookie (name) {
      const m = window.document.cookie.match('(^|;)\s
      ' + name + '\s
      =\s*([^;])\s(;|$)')
      return m ? decodeURIComponent(m[2]) : null
      }

    /**

    • Convert cookie/local storage ID data for bid adapters
    • @param {Object|string|number} idData
    • @param {IdSubmodule} submodule
      */
      function addIdDataToBids (idData, submodule) {
      const bidRequestIdData = submodule.decode(idData)
      if (bidRequestIdData) {
      // add id data for appending to bidRequests
      extendedBidRequestData.push(bidRequestIdData)
      } else {
      // log no value decoded for id submodule
      }
      }

    /**

    • ID submodule getId complete handler

    • @param {Object|string|number} response

    • @param {Object} data - config data for sub-moduel

    • @param {IdSubmodule} submodule
      */
      function getIdComplete (response, data, submodule) {
      if (response) {

       // get expires value from response first if possible, next use a config expires value if defined, lastly use the default
       const expires = response.expires || data.storage.expires || submodule.expires
       
       // save response id data to local storage
       const savedId = saveLocalStorageValue(response, data.storage.name, data.storage.type, expires)
       if (savedId) {
           const idData = getLocalStorageValue(data.storage.name)
           if (idData) {
               // decode is called and it's result added to the array to be added to bid requests
               addIdDataToBids(idData, submodule)
               
               // add hook if not added previously
               if (extendedBidRequestData.length === 0) {
                   $$PREBID_GLOBAL$$.requestBids.addHook(requestBidHook)
               }
           } else {
               // log error, invalid id data returned after saving to local storage, skip this id submodule
           }
       } else {
           // log error saving to local storage, skip this id submodule
       }
      

      } else {
      // log error web request for id failed
      }
      }

    /**

    • init user id module if config values are set correctly
      */
      function initUserId () {
      // check if cookie/local storage is active
      if (!localStorageEnabled()) {
      // exit if no cookies or local storage
      return
      }

      // check if any user id types are set in configuration (must opt-in to enable)
      if (!Array.isArray(config.get('usersync.userIds'))) {
      // exit if no configurations are set
      return
      }

      [pubCommonId, unifiedId].forEach(function(submodule) {
      // try to get config for id submodule
      const submoduleConfig = config.get('usersync.userIds').find(userIdConfig => userIdConfig.name === submodule.configKey)
      if (!submoduleConfig) {
      // log error, config not found for submodule, skip this id submodule
      return
      }

       // get storage key from config if set
       const storageName = submoduleConfig.storage.name
       if (!storageName) {
           // log error, key skip this id submodule
           return
       }
      
       // get storage type from config if set
       const storageType = submoduleConfig.storage.type
       if (!storageType) {
           // log error, key skip this id submodule
           return
       }
      
       const url = submoduleConfig.params.url
       if (!url) {
           // possibly log if url not found in config (though maybe not since pubCommonId does not need this functionality)
       }
      
       const syncDelay = submoduleConfig.syncDelay || 0
       if (!syncDelay) {
           // log sync delay not found in config
       }
       const storedId = getLocalStorageValue(storageName, submoduleConfig.storage.type)
       // If ID from local storage/cookies is valid, make data available to bid requests
       if (storedId) {
           addIdDataToBids(storedId, submodule)
       } else {
           // Else, ID does not exist in local storage, call the submodule getId to get a value to save to local storage
           // Note: syncDelay implemented here by wrapping with a timer
           if (syncDelay) {
               setTimeout(function() {
                   submodule.getId(data, function(response) {
                       // call webserviceComplete passing storageName, submodule, expires, and the response from the callback
                       getIdComplete(response, data, submodule)
                   })
               }, syncDelay)
           } else {
               submodule.getId(data, function(response) {
                   // call webserviceComplete passing config data, and the response from the callback
                   getIdComplete(response, data, submodule)
               })
           }
       }
      

      })

      // add hook only if data is going to be passed at this point, else add hook in getId complete
      if (extendedBidRequestData.length) {
      $$PREBID_GLOBAL$$.requestBids.addHook(requestBidHook)
      }
      }

    // call init
    initUserId()

    
    
    ## Constants
    
    <dl>
    <dt><a href="#pubCommonId">pubCommonId</a> : <code><a href="#IdSubmodule">IdSubmodule</a></code></dt>
    <dd><p>PubCommonID Module</p>
    </dd>
    <dt><a href="#unifiedID">unifiedID</a> : <code><a href="#IdSubmodule">IdSubmodule</a></code></dt>
    <dd><p>unifiedID Module</p>
    </dd>
    <dt><a href="#extendedBidRequestData">extendedBidRequestData</a> : <code>Array.&lt;Object&gt;</code></dt>
    <dd><p>ID data for appending to bid requests from the requestBidHook</p>
    </dd>
    </dl>
    
    ## Functions
    
    <dl>
    <dt><a href="#requestBidHook">requestBidHook(config, next)</a> ⇒ <code>*</code></dt>
    <dd><p>Decorate ad units with user id properties. This hook function is called before the
    real pbjs.requestBids is invoked, and can modify its parameter</p>
    </dd>
    <dt><a href="#localStorageEnabled">localStorageEnabled()</a> ⇒ <code>boolean</code> | <code>*</code></dt>
    <dd><p>Helper to check if local storage or cookies are enabled
    Question: Should we add the local storage methods to utils?</p>
    </dd>
    <dt><a href="#saveLocalStorageValue">saveLocalStorageValue(data, name, storageType, expires)</a> ⇒ <code>boolean</code></dt>
    <dd><p>Helper to set key va
    lue to from local storage or cookies as fallback
    Question: Should we add the local storage methods to utils?</p>
    </dd>
    <dt><a href="#getLocalStorageValue">getLocalStorageValue(name, type)</a> ⇒ <code>*</code></dt>
    <dd></dd>
    <dt><a href="#setCookie">setCookie(name, value, expires)</a></dt>
    <dd><p>Helper to set key value in cookies
    Question: Should we add the cookie methods to utils?</p>
    </dd>
    <dt><a href="#getCookie">getCookie(name)</a> ⇒ <code>*</code></dt>
    <dd><p>Helper get value for key from cookies
    Question: Should we add the cookie methods to utils?</p>
    </dd>
    <dt><a href="#addIdDataToBids">addIdDataToBids(idData, submodule)</a></dt>
    <dd><p>Convert cookie/local storage ID data for bid adapters</p>
    </dd>
    <dt><a href="#getIdComplete">getIdComplete(response, data, submodule)</a></dt>
    <dd><p>ID submodule getId complete handler</p>
    </dd>
    <dt><a href="#initUserId">initUserId()</a></dt>
    <dd><p>init user id module if config values are set correctly</p>
    </dd>
    </dl>
    
    ## Typedefs
    
    <dl>
    <dt><a href="#webserviceCallback">webserviceCallback</a> : <code>function</code></dt>
    <dd></dd>
    <dt><a href="#getId">getId</a> : <code>function</code></dt>
    <dd></dd>
    <dt><a href="#decode">decode</a> ⇒ <code>Object</code> | <code>string</code> | <code>number</code></dt>
    <dd></dd>
    <dt><a href="#IdSubmodule">IdSubmodule</a> : <code>Object</code></dt>
    <dd></dd>
    </dl>
    
    <a name="pubCommonId"></a>
    
    ## pubCommonId : [<code>IdSubmodule</code>](#IdSubmodule)
    PubCommonID Module
    
    **Kind**: global constant  
    <a name="unifiedID"></a>
    
    ## unifiedID : [<code>IdSubmodule</code>](#IdSubmodule)
    unifiedID Module
    
    **Kind**: global constant  
    <a name="extendedBidRequestData"></a>
    
    ## extendedBidRequestData : <code>Array.&lt;Object&gt;</code>
    ID data for appending to bid requests from the requestBidHook
    
    **Kind**: global constant  
    <a name="requestBidHook"></a>
    
    ## requestBidHook(config, next) ⇒ <code>\*</code>
    Decorate ad units with user id properties. This hook function is called before the
    real pbjs.requestBids is invoked, and can modify its parameter
    
    **Kind**: global function  
    
    | Param | Type |
    | --- | --- |
    | config | <code>PrebidConfig</code> | 
    | next |  | 
    
    <a name="localStorageEnabled"></a>
    
    ## localStorageEnabled() ⇒ <code>boolean</code> \| <code>\*</code>
    Helper to check if local storage or cookies are enabled
    Question: Should we add the local storage methods to utils?
    
    **Kind**: global function  
    <a name="saveLocalStorageValue"></a>
    
    ## saveLocalStorageValue(data, name, storageType, expires) ⇒ <code>boolean</code>
    Helper to set key va
    lue to from local storage or cookies as fallback
    Question: Should we add the local storage methods to utils?
    
    **Kind**: global function  
    
    | Param | Type | Description |
    | --- | --- | --- |
    | data | <code>Object</code> \| <code>string</code> \| <code>number</code> |  |
    | name | <code>string</code> |  |
    | storageType | <code>string</code> | either 'cookie' or 'localStorage' |
    | expires | <code>number</code> |  |
    
    <a name="getLocalStorageValue"></a>
    
    ## getLocalStorageValue(name, type) ⇒ <code>\*</code>
    **Kind**: global function  
    
    | Param |
    | --- |
    | name | 
    | type | 
    
    <a name="setCookie"></a>
    
    ## setCookie(name, value, expires)
    Helper to set key value in cookies
    Question: Should we add the cookie methods to utils?
    
    **Kind**: global function  
    
    | Param | Type |
    | --- | --- |
    | name | <code>string</code> | 
    | value | <code>string</code> \| <code>number</code> | 
    | expires | <code>number</code> | 
    
    <a name="getCookie"></a>
    
    ## getCookie(name) ⇒ <code>\*</code>
    Helper get value for key from cookies
    Question: Should we add the cookie methods to utils?
    
    **Kind**: global function  
    
    | Param | Type |
    | --- | --- |
    | name | <code>string</code> | 
    
    <a name="addIdDataToBids"></a>
    
    ## addIdDataToBids(idData, submodule)
    Convert cookie/local storage ID data for bid adapters
    
    **Kind**: global function  
    
    | Param | Type |
    | --- | --- |
    | idData | <code>Object</code> \| <code>string</code> \| <code>number</code> | 
    | submodule | [<code>IdSubmodule</code>](#IdSubmodule) | 
    
    <a name="getIdComplete"></a>
    
    ## getIdComplete(response, data, submodule)
    ID submodule getId complete handler
    
    **Kind**: global function  
    
    | Param | Type | Description |
    | --- | --- | --- |
    | response | <code>Object</code> \| <code>string</code> \| <code>number</code> |  |
    | data | <code>Object</code> | config data for sub-moduel |
    | submodule | [<code>IdSubmodule</code>](#IdSubmodule) |  |
    
    <a name="initUserId"></a>
    
    ## initUserId()
    init user id module if config values are set correctly
    
    **Kind**: global function  
    <a name="webserviceCallback"></a>
    
    ## webserviceCallback : <code>function</code>
    **Kind**: global typedef  
    
    | Param | Type | Description |
    | --- | --- | --- |
    | response | <code>Object</code> | assumed to be a json object |
    
    <a name="getId"></a>
    
    ## getId : <code>function</code>
    **Kind**: global typedef  
    **Summary**: submodule interface for getId function  
    
    | Param | Type | Description |
    | --- | --- | --- |
    | data | <code>Object</code> |  |
    | callback | [<code>webserviceCallback</code>](#webserviceCallback) | optional callback to execute on id retrieval |
    
    <a name="decode"></a>
    
    ## decode ⇒ <code>Object</code> \| <code>string</code> \| <code>number</code>
    **Kind**: global typedef  
    **Summary**: submodule interface for decode function  
    
    | Param | Type |
    | --- | --- |
    | idData | <code>Object</code> \| <code>string</code> \| <code>number</code> | 
    
    <a name="IdSubmodule"></a>
    
    ## IdSubmodule : <code>Object</code>
    **Kind**: global typedef  
    **Properties**
    
    | Name | Type |
    | --- | --- |
    | configKey | <code>string</code> | 
    | expires | <code>number</code> | 
    | decode | [<code>decode</code>](#decode) | 
    | getId | [<code>getId</code>](#getId) | 
    
@mike-chowla
Copy link
Contributor

@bretg What's your thinking about whether the default config is before the bid request or after?

For sync delay for bid request, is there a reason to have separate timer? An option that keeps things simpler would be run the OpenID sync with the rest of the adapter syncs.

@bretg
Copy link
Collaborator Author

bretg commented Sep 7, 2018

Discussed with some members of OpenID consortium and RubiconProject internal. Changed:

  • Switched default to opt-in
  • Added delayAuctionStart option as more clearly defined behavior
  • made openId config a child of usersync so they can share syncDelay. (thanks @mike-chowla)

@mkendall07
Copy link
Member

Looks like a great start - thanks Bret!

  1. Suggest we rename delayAuctionStart to something like maxDelayToAuction - indicating that we will trigger the auction ASAP if we get a quick response from openID or until max time is hit.
  2. Should we make this more generic to support other universal formats? I think the attributes are generic enough - we can stick the config into an array:
usersync: {
  universalIds: [{
    name: 'openId',
    url: 'URL_TO_OPEN_ID_SERVER',
    storage: {
      type: 'html5',
      name: 'pbjs-openid'
    },
    maxDelayToAuction: 500
  }]
}

Not sure if that's a use case we'd ever need to support though.

@snapwich
Copy link
Collaborator

snapwich commented Sep 7, 2018

This looks similar to the already existing pubCommonId module so we should probably make this generic enough to support multiple implementations.

Right now the pubCommonId uses the requestBids hook to add its extension. Don't know if we want to do something similar here or add a more specific extension point for shared ids.

@mkendall07
Copy link
Member

mkendall07 commented Sep 7, 2018

+1 to Rich's comment. I think we can cover pubCommonId with the proposed structure above.

Edit: We'd probably remove pubCommonId as a separate module and either

  1. Put the logic into core generically to support OpenID / pubCommonId / other provider
  2. Create a separate module for the same functionality

@bretg
Copy link
Collaborator Author

bretg commented Sep 7, 2018

thanks for the input. Description updated:

  • 'universalIds' is an array under usersync
  • supported only if the module is loaded

@bretg bretg changed the title Prebid.js and OpenID Prebid.js, OpenID, and other universal IDs Sep 17, 2018
@mike-chowla
Copy link
Contributor

@mkendall07 I think it's worth making the mechanism generic enough to support multiple providers given that's far from clear which id providers publishers will want to use, especially with AppNexus leaving the Advertising ID Consortium

@mkendall07
Copy link
Member

@mike-chowla yes Bret updated the proposal to be generic.

@stale
Copy link

stale bot commented Oct 11, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Oct 11, 2018
@bwoolcott bwoolcott removed the stale label Oct 11, 2018
@bretg
Copy link
Collaborator Author

bretg commented Oct 24, 2018

Made an update to move the "url" attribute underneath a "params" object and add a "partner" attribute as well.

Looks like the TradeDesk is going to allow us to use their URL as a default for OpenId - will post that once confirming. Once that happens, params.partner attribute would be a way to specify their "partner id" rather than the whole URL.

@pycnvr
Copy link
Collaborator

pycnvr commented Oct 29, 2018

Looks good. It's great that GDPR for publishers will be taken into account. A few questions from pubCommonId perspective.

  1. PubCommonId doesn't reach out to servers to obtain or sync ID's. Rather, the id is generated locally and stored. I guess the logic flow would be something like this: Universal ID module checks storage for id first. If not found then wait till the sync phase and pass the params object to get ID. This means if the ID was missing or expired, then there is a one-cycle delay. Would it be possible to accommodate submodules that do not require syncs?

  2. Would submodule custom parameters be a part of params section?

  3. More on storage. Should pubCommonId still store its own cookie? If not, then could the storage period be extended beyond 30 days?

@bretg
Copy link
Collaborator Author

bretg commented Nov 8, 2018

Ok - I took a cut at integrating all features of PubCommonID into this proposal.

@mkendall07 - this is now complicated enough to warrant a review. Perhaps we can do that in the next PBJS PMC meeting?

@bretg
Copy link
Collaborator Author

bretg commented Nov 16, 2018

Spoke with DigiTrust - added a couple of minor tweaks to support their involvement in this.

@goosemanjack
Copy link
Contributor

Hello. I've recently come on to supporting the DigiTrust id system via IAB Tech Lab. One of our high priorities is integration with Prebid, so to that end I'll be picking everyone's brains on how to do that correctly.

In reading this thread it isn't clear if a consensus was reached. Is the plan to build on pubCommonId module, or to implement the pseudo code for UniversalId outlined in the spec? Forgive me if some questions appear dense. I'm just starting to dig into this code.

@bretg
Copy link
Collaborator Author

bretg commented Dec 5, 2018

@goosemanjack - we're building this new "universal ID" module that has a a simple requirement for supporting each additional ID system must be implementable using a small amount of code, say ~0.25 KB. The framework does most of the work -- the only code that each ID system provides is:

  • getId function - either generate the ID locally or call out to a web service to obtain the user's ID. The results of this call are stored by the framework in the define local storage.
  • a decode function that parses values local storage for what should be passed to the adapters

Isaac has provided much of the general code already in the opening comment - it seems likely that we'll release with OpenID and PubCommon, and when you have DigiTrust ready we'll add it. If you have a webservice all ready to go, then you can pile on to this release.

@goosemanjack
Copy link
Contributor

Great. In most cases the DigiTrust ID will be cryptographically generated on the browser client. Some edge cases with Chrome over non-ssl connections require our web service. We may need to talk more about your file size constraints. Use of DigiTrust IDs is restricted to members and we utilize an iframe to make the ID portable across different member publisher sites without the need for sync calls or server hits. The framework also provides some functionality around GDPR compliance and opt-out that we should insure is otherwise covered in Prebid or integrated from DigiTrust into Prebid.

On a more technical note, it appears prebid is using ES6 syntax for modules. We are currently CommonJS/ES5. It is my understanding that we will need to upgrade to ES6 syntax in order to integrate into your build system. If anyone has insight where that wouldn't be the case, please LMK.

Thanks.

@mkendall07
Copy link
Member

this is LGTM from a spec proposal - I'd have some comments on the implementation but let's do that as a formal review process. @bretg Feel free to open the PR.

@bretg bretg changed the title Prebid.js, OpenID, and other universal IDs Prebid.js, UnifiedID, and other universal IDs Dec 14, 2018
@bretg
Copy link
Collaborator Author

bretg commented Dec 14, 2018

They renamed OpenId to UnifiedId. Updated.

@bretg
Copy link
Collaborator Author

bretg commented Dec 19, 2018

Note: added a feature to let the gdprConsent object be available to the getId function. We'll update the unifiedId getId function to use it

@bretg
Copy link
Collaborator Author

bretg commented Dec 20, 2018

@pycnvr noted in prebid/prebid.github.io#1068 that the PubCommonID requires an "optout" cookie flag, which is the per-user ability to avoid setting IDs for sites that don't have a GDPR-compliant CMP. Added this item above:

For sites that don't support GDPR, the module should also support a cookie-based opt-out 
like the PubCommonID module does. Specifically, if there exists a first party cookie called 
"_pbjs_id_optout" (with any value), then this module becomes inactive for this particular 
user just like if there was a CMP without Purpose 1 consent.

@idettman idettman mentioned this issue Jan 7, 2019
1 task
@bretg
Copy link
Collaborator Author

bretg commented Jan 22, 2019

@bretg bretg changed the title Prebid.js, UnifiedID, and other universal IDs Prebid.js, UnifiedID, and other user IDs Jan 29, 2019
@mercuryyy
Copy link
Contributor

@bretg https://staging.prebid.org/dev-docs/modules/universalid.html - 404

when building PB with userID from download page - {"error":"Prebid file not built properly","requestId":"d99fd837-0fa9-41b4-8fa6-98386173c118"}

@avuim
Copy link

avuim commented Apr 12, 2019

@bretg: Is there a timeline for release of this module?

@bretg
Copy link
Collaborator Author

bretg commented Apr 12, 2019

Actually, it was released on Weds with PBJS 2.10. Documentation at https://prebid.org/dev-docs/modules/userId.html

Currently only two adapters support Unified ID - would love to see others add support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants