GDPR Cookie control for web developers

Don't bore us, get to the chorus

Dunno about you, but I don't feel safe wandering the streets of the Internet without Firefox, Privacy Badger and uBlock Origin. Browsing without these three in place (in a metaphor I'm confident will upset no-one) would be like walking down a street without a facemask.

One of the side effects of using these plugins is that they keep a running total of the stuff they've blocked, in a wee counter above the icon. This has given me a sort of gauge of just how compliant (or not) websites are of the GDPR rules.

Privacy notifications from Privacy Badger and uBlock Origin.

Only a tiny part of the rules refer to cookies, but my reading of it is "don't use any tracking cookies until the user has explicitly opted in to them". Despite this, a huge number of sites get such cookies blocked by uBlock Origin before I've consented to have them on my hard drive (spoiler: I don't consent).

Current cookie control services

All of the current cookie controls are - understandably - aimed at those working in client service. They allow users to dynamically add JavaScript to their site and sort it into different categories, all without bothering a web developer. As a result, these controls are either branded or require an ongoing cost.

What surprised me is that there doesn't seem to be a genuinely free, client-side, fully-customisable cookie control which you can download once and use forever.

So I wrote one.

Premise

This is how I see an ideal flow:

  1. The user arrives at the page
  2. No tracking cookies are added by the site
  3. The user is asked if they accept cookies or not
  4. Their preference is stored
  5. After that point, tracking cookies are stored (or not)
  6. Everyone just goes about their day

Setup

My cookie control starts by creating a namespace called gdpr and attaching it to the window object.

It adds a function called addTag which lets developers inject HTML tags into the page at the same location where the function is called.

This all needs to happen before any other JavaScript gets added to the site, ideally within the head element.

Cookie check

The control uses a single cookie to store the user preferences on the site. This can hold three values:

  1. null - the user has yet to make a choice
  2. true - the user is happy to accept all cookies
  3. false - the user doesn't want any cookies, thank you very much

(note that other more fully-featured cookie controls allow you to sort different cookies into different categories, but this was more functionality than I needed)

This cookie is checked once, as early as possible, and this value is written to the gdpr namespace. I do this because my guess is checking a cookie is expensive and time consuming, where as the window object is a bit more local.

Importing JavaScript (or anything else)

Now it's just a case of adding if statements around elements you want to include. For example, here's how to load in jQuery from a CDN:

<script
  src="https://code.jquery.com/jquery-3.5.1.min.js"
  integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
  crossorigin="anonymous">
</script>

(I appreciate that this is an unrealistic example as jQuery doesn't bake any cookies, but it's more about how you can include multiple attributes onto the script tag)

Here's how you could import jQuery using the cookie control:

<script>
if (window.gdpr.choice) {
  window.gdpr.addTag('script',{
    'src': 'https://code.jquery.com/jquery-3.5.1.min.js',
    'integrity': 'sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=',
    'crossorigin': 'anonymous'
  });
}
</script>

Services such as Disqus are even simpler (and don't even require the addTag() function):

<script>
<div id="disqus_thread"></div>
<script>
if (window.gdpr.choice) {
  (function() { // DON'T EDIT BELOW THIS LINE
  var d = document, s = d.createElement('script');
  s.src = 'https://EXAMPLE.disqus.com/embed.js';
  s.setAttribute('data-timestamp', +new Date());
  (d.head || d.body).appendChild(s);
  })();
}
</script>
<noscript>Please enable JavaScript to view the
<a href="https://disqus.com/?ref_noscript">
comments powered by Disqus.</a></noscript>
</script>

Adding YouTube videos

addTag() isn't just limited to script tags, of course. Here's how you could add a YouTube video:

if (window.gdpr.choice) {
  window.gdpr.addTag('iframe',{
      'width': '560',
      'height': '315',
      'src': 'https://www.youtube.com/embed/gsQQgfRay88',
      'frameborder': 0,
      'allow': 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
      'allowfullscreen': true
  });
} else {
  window.gdpr.addTag('p','','Please enable cookies to see this YouTube video');
}

Note how you can use an else statement to serve up alternate content. Also, it's possible to add YouTube videos in privacy-enhanced mode, but you might not want to do this for $$$ reasons. I won't judge you.

Other JavaScript

Finally, there's a JavaScript file which can be included anywhere towards the end of the page. This does the following:

  • Double-checks that the gdpr namespace exists (perhaps the code hasn't been added correctly)
  • Creates the markup for your cookie control and adds it to your page
  • Sets up the click events on the yes please and no thanks buttons
  • Sets up the click event for the button for the user to change their mind

How does this work?

Because that if statement is checking the value of the window.gdpr.choice variable, it won't let any code through which doesn't have a true or truthy value. As both false and null are falsey values, nothing gets loaded in without the explicit say-so of the user.

Cookie lifetime

The regulations say that we can only assume that consent is given for no longer than one year. After that time, it needs to be requested again. The JavaScript will ask for consent two days before the deadline for daft code optimisation reasons.

Other features

The regulations say that users need to be given the option to change their mind (without waiting nearly a year). Users can call up the cookie control element by clicking on an element with a data-js="cookie-config" attribute. This usually gets hidden in the footer, but could hover in the corner of the viewport, if you prefer.

Limitations

Cookies load on second page impression

I built this with a focus on performance. This means that even after the user has accepted cookies, the external JavaScript won't load immediately: a full page refresh (or more realistically, navigating to a new page) is required. This means that the user's first experience of your site will be as unencumbered as possible. Think of it as the trial version of the site. The cache is nicely warmed up with all your local CSS, JavaScript, images and typefaces. It loads really fast. The user says to themselves yeah, let's accept this site's cookies, then they navigate to the next page with the additional overhead of whatever lurks in GTM.

Some cookie controls take a similar approach and force a page refresh after the choice is made. It would also be possible to have a slightly more elegant solution: to loop through the script tags on the page and see if there's anything which can load, based on the user's choice to opt-in. But that's not how this currently works.

Cookies are not sorted by type

You've probably seen cookie controls which bewilder the user by offering them options to opt in to cookies in some categories, but not others. I've no idea how this might benefit the user (and I suspect this is part of some dark UX pattern). This doesn't have that functionality.

This is not for folk who dislike code

This does not have a nice front end. It's code, all the way down. This pushes the management of cookies directly onto the slumped shoulders of your web developer.

Let me see the code

This looks as dull as possible for good reason: you have complete freedom to style it as you need. Note that for boring iframe reasons, you might get better results testing the debug version of that CodePen, rather than the fancy one below.