Check if third-party cookies are enabled

前端 未结 6 1375
既然无缘
既然无缘 2020-11-27 10:58

I have an application that needs to check whether the client browser has third-party-cookies enabled. Does anyone know how to do this in JavaScript?

相关标签:
6条回答
  • 2020-11-27 11:10

    Technical Background

    The third party sets & reads cookies over HTTP (not in JavaScript).

    So we need two requests to an external domain to test if third-party cookies are enabled:

    1. One where the third party sets the cookie(s)
    2. The second, with a differing response depending on whether the browser sent the cookie(s) back to the same third party in a second request.

    We cannot use XMLHTTPRequest (Ajax) because of the DOM security model.

    Obviously you can't load both scripts in parallel, or the second request may be made before the first request’s response makes it back, and the test cookie(s) will not have been set.

    Code Example

    Given:

    1. The .html file is on one domain, and

    2. The .js.php files are on a second domain, we have:

    The HTML test page

    Saved as third-party-cookies.html

    <!DOCTYPE html>
    <html>
    <head id="head">
      <meta charset=utf-8 />
      <title>Test if Third-Party Cookies are Enabled</title>
    <style type="text/css">
    body {
      color: black;
      background: white none;
    }
    .error {
      color: #c00;
    }
    .loading {
      color: #888;
    }
    .hidden {
      display: none;
    }
    </style>
    <script type="text/javascript">
    window._3rd_party_test_step1_loaded = function(){
      // At this point, a third-party domain has now attempted to set a cookie (if all went to plan!)
      var step2Url = 'http://third-party.example.com/step2.js.php',
        resultsEl = document.getElementById('3rd_party_cookie_test_results'),
        step2El = document.createElement('script');
    
      // Update loading / results message
      resultsEl.innerHTML = 'Stage one complete, loading stage 2&hellip;';
      // And load the second part of the test (reading the cookie)
      step2El.setAttribute('src', step2Url);
      resultsEl.appendChild(step2El);
    }
    window._3rd_party_test_step2_loaded = function(cookieSuccess){
      var resultsEl = document.getElementById('3rd_party_cookie_test_results'),
        errorEl = document.getElementById('3rd_party_cookie_test_error');
      // Show message
      resultsEl.innerHTML = (cookieSuccess ? 'Third party cookies are <b>functioning</b> in your browser.' : 'Third party cookies appear to be <b>disabled</b>.');
    
      // Done, so remove loading class
      resultsEl.className = resultsEl.className.replace(/\bloading\b/,' ');
      // And remove error message
      errorEl.className = 'hidden';
    }
    </script>
    </head>
    <body id="thebody">
    
      <h1>Test if Third-Party Cookies are Enabled</h1>
    
      <p id="3rd_party_cookie_test_results" class='loading'>Testing&hellip;</p>
      <p id="3rd_party_cookie_test_error" class="error hidden">(If this message persists, the test could not be completed; we could not reach the third-party to test, or another error occurred.)</p>
    
      <script type="text/javascript">
      window.setTimeout(function(){
        var errorEl = document.getElementById('3rd_party_cookie_test_error');
        if(errorEl.className.match(/\berror\b/)) {
          // Show error message
          errorEl.className = errorEl.className.replace(/\bhidden\b/,' ');
        } else {
        }
      }, 7*1000); // 7 sec timeout
      </script>
      <script type="text/javascript" src="http://third-party.example.com/step1.js.php"></script>
    </body>
    </html>
    

    The first third-party JavaScript file

    Saved as step1.js.php

    This is written in PHP so we can set cookies as the file loads. (It could, of course, be written in any language, or even done in server config files.)

    <?php
      header('Content-Type: application/javascript; charset=UTF-8');
      // Set test cookie
      setcookie('third_party_c_t', 'hey there!', time() + 3600*24*2);
    ?>
    window._3rd_party_test_step1_loaded();
    

    The second third-party JavaScript file

    Saved as step2.js.php

    This is written in PHP so we can read cookies, server-side, before we respond. We also clear the cookie so the test can be repeated (if you want to mess around with browser settings and re-try).

    <?php
      header('Content-Type: application/javascript; charset=UTF-8');
      // Read test cookie, if there
      $cookie_received = (isset($_COOKIE['third_party_c_t']) && $_COOKIE['third_party_c_t'] == 'hey there!');
      // And clear it so the user can test it again 
      setcookie('third_party_c_t', '', time() - 3600*24);
    ?>
    window._3rd_party_test_step2_loaded(<?php echo ($cookie_received ? 'true' : 'false'); ?>);
    

    The last line uses the ternary operator to output a literal Javascript true or false depending on whether the test cookie was present.

    Test it here.

    Available for your testing pleasure at https://alanhogan.github.io/web-experiments/3rd/third-party-cookies.html.

    (As a final note — don’t use someone else’s server to test third-party cookies without their permission. It could break spontaneously or inject malware. And it’s rude.)

    0 讨论(0)
  • 2020-11-27 11:10

    My solution works by loading a <script> from an external domain that sets a cookie, checks if it was successful, and then passes the result (1 or 0) as an argument to a callback function.

    The HTML:

    <script>
    function myCallback(is_enabled) {
        if (is_enabled===1) {//third party cookies are enabled
        }
    }
    </script>
    <script src="https://third-party-domain/third-party-cookies.php?callback=myCallback"></script>
    

    If you prefer to run it asynchronously, you may use the async and defer attributes.

    This also works with jQuery:

    <script>
    $.ajax({
        url: 'https://third-party-domain/third-party-cookies.php',
        dataType: 'jsonp',
    }).done(function(is_enabled) {
        if (is_enabled===1) {//third party cookies are enabled
        }
    })
    </script>
    

    Here is the third-party-cookies.php code. This must be hosted on a different domain. The server must support PHP:

    <?php
    
    header('Cache-Control: no-store');
    header('Content-Type: text/javascript');
    
    if ($_GET['callback']=='') {
        echo 'alert("Error: A callback function must be specified.")';
    }
    elseif (!isset($_GET['cookieName'])) {// Cookie not set yet
        $cookieName = strtr((string)$_SERVER['UNIQUE_ID'], '@', '_');
        while (isset($_COOKIE[$cookieName]) || $cookieName=='') {
            $cookieName = dechex(mt_rand());// Get random cookie name
        }
        setcookie($cookieName, '3rd-party', 0, '/');
        header('Location: '.$_SERVER['REQUEST_URI'].'&cookieName='.$cookieName);
    }
    elseif ($_COOKIE[$_GET['cookieName']]=='3rd-party') {// Third party cookies are enabled.
        setcookie($_GET['cookieName'], '', -1, '/'); // delete cookie
        echo $_GET['callback'].'(1)';
    }
    else {// Third party cookies are not enabled.
        echo $_GET['callback'].'(0)';
    }
    
    0 讨论(0)
  • 2020-11-27 11:17

    In theory you'd just have a page call out somewhere that would set a thirdparty cookie and then check for that cookie's existence. However, standard browser security does not allow scripts from domain A to do anything with cookies set on domains B,C,etc... e.g. you can't access "foreign" cookies.

    If you have some specific usage in mind, such as checking if ads are blocked (which would also block the 3rd party tracking cookie), you could check if the ad server's content is within the page's DOM, but you couldn't see if the cookie's there.

    0 讨论(0)
  • 2020-11-27 11:23

    Third Party Cookie detection with Whitelisting of URLs

    Alan H & Gojko Adzic are good enough for majority of use cases, but these solutions won't work if you want your users to do whitelisting of third party cookies only to certain domains.

    I'm presenting a slightly modified version of Gojko Adzic's answer

    For this we need two domains :

    • Domain 1, this is the page your user lands, it initially sets tpc=pending, then it redirects to Domain 2
    • Domain 2 injects Domain 1's url in an iFrame, tries to set a cookie tpc=true and redirects back to Domain 1
    • Now, Domain 1 reads the cookie tpc and checks if its true, if we get value as true, third party cookies are allowed if it is still in pending third party cookies are blocked blocked.

    Now, you might ask your users to whitelist (allow third party cookies) ONLY for Domain 1, also by this technique third party detection will be accurate if the users had white listed your domain.


    This is tested in Chrome 74, 75, 76 & Edge 78

    Unfortunately, Mozilla doesn't provide whitelisting of urls like Chrome does and Safari has it own mechanisms of detecting third party cookies (ITP).

    P.S. Will upload this demo in my github when I get time.

    0 讨论(0)
  • 2020-11-27 11:31

    ⚠️ Update: Multiple browsers are now implementing stricter privacy controls that may cause this test to give a "false negative" result, meaning that it will conclude that third-party cookies are disabled when they may in fact be enabled but blocked in use-cases similar to that of the test.


    Here's a pure JS solution not requiring any server-side code, so it can work from a static CDN: https://github.com/mindmup/3rdpartycookiecheck - the first script sets the cookie in the code, then redirects to a second script that will post a message to the parent window.

    You can try out a live version using https://jsfiddle.net/tugawg8y/

    client-side HTML:

    third party cookies are <span id="result"/>
    <iframe src="https://mindmup.github.io/3rdpartycookiecheck/start.html"
        style="display:none" />
    

    client-side JS:

     var receiveMessage = function (evt) {
       if (evt.data === 'MM:3PCunsupported') {
         document.getElementById('result').innerHTML = 'not supported';
       } else if (evt.data === 'MM:3PCsupported') {
         document.getElementById('result').innerHTML = 'supported';
       }
     };
     window.addEventListener("message", receiveMessage, false);
    

    Of course, this requires that the client runs JavaScript which is a downside compared to the server-based solutions; on the other hand, it's simpler, and you were asking about a JS solution.

    0 讨论(0)
  • 2020-11-27 11:33

    Alan's solution is great, but you don't have to use PHP or any other server-side programming language.

    At least if you use nginx. :)

    This is a pure* nginx server-side configuration for Alan's solution:

    Nginx config start

    server {
        listen 80;
        server_name third-party.example.com
    
        # don't allow user's browser to cache these replies
        expires -1;
        add_header Cache-Control "private";
        etag off;
    

    The first third-party "JavaScript file" - served by nginx

        location = /step1.js.php {
            add_header Content-Type 'application/javascript; charset=UTF-8';
    
            add_header Set-Cookie "third_party_c_t=hey there!;Max-Age=172800";
    
            return 200 'window._3rd_party_test_step1_loaded();';
        }
    

    The second third-party "JavaScript file" - served by nginx

        location = /step2.js.php {
            add_header Content-Type 'application/javascript; charset=UTF-8';
    
            set $test 'false';
            if ($cookie_third_party_c_t = 'hey there!') {
                set $test 'true';
                # clear the cookie
                add_header Set-Cookie "third_party_c_t=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
            }
    
            return 200 'window._3rd_party_test_step2_loaded($test);';
        }
    

    Nginx config end

    }
    


    Side notes:

    • Yeah, yeah, I know that IfIsEvil,
    • I kept the names ending with ".php" for complete compatibility with Alan's "The HTML test page" (third-party-cookies.html),
    • You can also move common "setting the Content-Type header" line of both locations to the server section (scope) of the config - I have kept it this way for the keep it more "1:1" translation.
    0 讨论(0)
提交回复
热议问题