HomeMethod

Knowledge

Web VitalsTechniquesSnippetsAnswersImpactsBottlenecks

Cloudflare Email Address Obfuscation

Get to now Cloudflare Email Address Obfuscation

What is Cloudflare Email Address Obfuscation?

Cloudflare Email Address Obfuscation, a service provided by Cloudflare, conceals email addresses from spam bots while ensuring visibility to human visitors on websites.

This is achieved by injecting a JavaScript code that decodes the email addresses.

The service aims to safeguard email addresses from basic page scrapping targeting “mailto:” contained on the href attribute on link elements.

How to enable Cloudflare email obfuscation?

The first step is to manage your DNS over Cloudflare.

Once done, all you need to do is enable it under:

Code
Domain settings page > Scrape Shield > Email Address Obfuscation

All you need to do is click the toggle box on the right side to become green.

It will autosave, so no need to look for a save button.

How does the obfuscation work?

Cloudflare utilizes hex encoding and encryption with a key to obfuscate email addresses.

In plain words, it changes:

Code
<a href="mailto:a@example.com">a@example.com</a>

To:

Code
<a href="https://adame.io/cdn-cgi/l/email-protection#[random-alpha-numerical-string]"><span class="__cf_email__" data-cfemail="[random-alpha-numerical-string]">[email&#160;protected]</span></a>

In addition to that, Cloudflare will add the following script to manage revealing the obfuscated email:

Code
<script data-cfasync="false" src="/cdn-cgi/scripts/[random-alpha-numerical-string]/cloudflare-static/email-decode.min.js"></script>

Once the above script executes, the encrypted email will then be revealed.

Web performance implications when using Cloudflare Email Address Obfuscation feature

The code itself doesn’t have a noticeable overhead once executed.

The main concern is when the email-decode.min.js file is loaded, in what conditions, and how long it takes for it to be ready.

Bad timing

Cloudflare’s intention is to prevent the email from being visible on the source code, but still, to be readily available on an early stage to avoid showing the wrong email to the visitor.

And it’s quite clear on how the script is injected.

It doesn’t have any defer attribute, any fetchpriority set to low in an attempt to control it’s loading.

Consequently, it gets assigned a fetchpriority set to high and will be part of the critical chain.

Code
URL /cdn-cgi/scripts/[random-alpha-numerical-string]/cloudflare-static/email-decode.min.js
Duration 2.319s (2.157s network transfer + 161.582ms resource loading)
Request Method GET
Initial Priority High
Priority High
Mime Type application/javascript
Encoded Data 1.1 kB
Decoded Body 1.2 kB

On slow networks, and low-end devices, that will impact First Contentful Paint (FCP) and Largest Contentful Paint (LCP) by delaying both and partially causing them to shift from one tier to another one.

Unnecessary effort at an early stage of the page load

Emails are not usually used within the viewport. Which leads us to question why the obfuscation script is even loaded that early.

I would have a better performance if they associated loading the script with intersection with the first occurrence of any obfuscated email using a threshold.

Like the following:

Code
// Select the element with the data-cfemail attribute
const targetElement = document.querySelector('[data-cfemail]');

// Function to be called when the element enters the viewport
const callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // Load the Cloudflare email-decode.min.js script
      loadScript('/cdn-cgi/scripts/[random-alpha-numerical-string]/cloudflare-static/email-decode.min.js');
      
      // Stop observing once the script is loaded
      observer.disconnect();
    }
  });
};

// Create an Intersection Observer
const observer = new IntersectionObserver(callback, {
  root: null, // Use the viewport as the root
  threshold: 0, // Trigger callback when any part of the target is visible
  rootMargin: '300px' // Add a 300px margin around the viewport
});

// Start observing the target element
if (targetElement) {
  observer.observe(targetElement);
}

// Function to load a script dynamically
function loadScript(url) {
  const script = document.createElement('script');
  script.src = url;
  document.body.appendChild(script);
}

Alternative to Cloudflare Email Address Obfuscation feature?

I built a code to help obfuscate email in WordPress using a shortcode called obfuscated_email.

I has 2 parameters, email and label. If the label is not used, it will be replaced by the email address.

Code
[obfuscated_email email="a@example.com" label="Whatever label you need it to be"]

And will render the following:

Code
<a data-obfuscation="true" data-email="bAfybnqmf/dpn" href="mailto:">Whatever label you need it to be</a>

If the visitor is within 300px from that link, it will turn to:

Code
<a data-obfuscation="revealed" href="mailto:a@example.com">Whatever label you need it to be</a>

There is a 3rd parameter called spam_email that can be used to expose a catch it all email. I’m personally always curious how my scrapped email will be used.

Code
[obfuscated_email email="a@example.com" label="Whatever label you need it to be" spam_email="spam-watchguard@example.com"]

The output will be:

Code
<a data-obfuscation="true" data-email="bAfybnqmf/dpn" data-watchdog="spam-watchguard@example.com" href="mailto:bAfybnqmf/dpn">Whatever label you need it to be</a>

The encoding strategy is quite simple.

I add one ASCII code for each character. This avoids using any Base64 encoding/decoding that are usually flagged as malicious.

Here is the full code to be added on your child theme’s functions.php file, or as a plugin, or on your favorite code snippet tool.

Goes without saying, always test on staging before deploying on your production website 🙂

I hope this helps you get your Email Obfuscation strategy up to speed!

I will leave you with the full code:

Code
<?php

// Encode function
function adx_obfuscation_encode_string($string)
{
    $encodedString = "";
    for ($i = 0; $i < strlen($string); $i++) {
        // Add 1 to the ASCII code of each character
        $encodedChar = chr(ord($string[$i]) + 1);
        $encodedString .= $encodedChar;
    }
    return $encodedString;
}

// Shortcode function
function adx_obfuscated_email_shortcode($atts)
{
    // Extract shortcode attributes
    $atts = shortcode_atts(
        [
            "email" => "",
            "spam_email" => "",
            "label" => "Click here", // Default label if not provided
        ],
        $atts,
        "obfuscated_email"
    );

    // Check if email attribute is provided
    if (!empty($atts["email"])) {
        // Encode emails
        $encoded_email = adx_obfuscation_encode_string($atts["email"]);
        $spam_email = $atts["spam_email"];

        // Escape the label to prevent XSS attacks
        $label = esc_html($atts["label"]);

        $spam_email_attribute = "";

        $mail_to = "";

        if ($spam_email === "") {
            $mail_to = $encoded_email;
        } else {
            $mail_to = $spam_email;
            $spam_email_attribute = 'data-watchdog="' . $spam_email . '" ';
        }

        if (empty($label)) {
            $label = $mail_to;
        }

        // Build the anchor tag
        $link =
            '<a data-obfuscation="true" data-email="' .
            $encoded_email .
            '" ' .
            $spam_email_attribute .
            'href="mailto:' .
            $mail_to .
            '">' .
            $label .
            "</a>";
        return $link;
    } else {
        return "Error: Email attribute is missing.";
    }
}

// Register the shortcode
add_shortcode("obfuscated_email", "adx_obfuscated_email_shortcode");

// Add JavaScript observer inline
function adx_obfuscated_email_observer_script()
{
    // Check if shortcode exists in the content
    global $post;
    static $added_script = false;
    if (
        !$added_script &&
        has_shortcode($post->post_content, "obfuscated_email")
    ) { ?>
        <style>
            a[data-obfuscation] { opacity: 0; }
            a[data-obfuscation="revealed"] { opacity: 1;}
        </style>
        <script>
            function decodeString(str) {
            return String.fromCharCode(...str.split('').map(x => x.charCodeAt(0) - 1));
            }

            function replaceContent(element, needle, replacement) {
                const content = element.outerHTML;
                const escapedNeedle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                const newContent = content.replace(new RegExp(escapedNeedle, 'g'), replacement);
                const tempElement = document.createElement('div');
                tempElement.innerHTML = newContent;
                tempElement.firstChild.removeAttribute("data-email");
                tempElement.firstChild.removeAttribute("data-watchdog");
                tempElement.firstChild.setAttribute("data-obfuscation", "revealed");
                element.parentNode.replaceChild(tempElement.firstChild, element);
            }

            const observer = new IntersectionObserver(entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting && entry.intersectionRatio > 0) {
                const element = entry.target;
                const email = decodeString(element.dataset.email);
                if (element.dataset.watchdog) {
                    replaceContent(element, element.dataset.watchdog, email);
                } else {
                    element.href = `mailto:${email}`;
                    replaceContent(element, element.dataset.email, email);
                }
                element.removeAttribute('data-email');
                element.removeAttribute('data-watchdog');
                element.setAttribute("data-obfuscation","revealed");
                console.log(element);
                observer.unobserve(element);
                }
            });
            });

            document.querySelectorAll('a[data-obfuscation]').forEach(element => observer.observe(element));

        </script>
        <?php $added_script = true;}
}
add_action("wp_footer", "adx_obfuscated_email_observer_script");
Support our family now