Menu

How to bypass Cloudflare

In this article you'll learn how to bypass Cloudflare's "Verifying you are human" page in NodeJS with Playwright browser automation.

Let's first briefly understand how Cloudflare works. Without Cloudflare, website administrators host their pages on a server with a public IP address. Their domain names resolve to that IP address, and your browser connects to their server directly.

When they use Cloudflare, they delegate DNS management to Cloudflare, which starts resolving domain names to Cloudflare's own IP addresses. On these IP addresses, they run special proxy servers that filter incoming HTTP and HTTPS requests.

New visitors see the well-known page with the text: "Verifying you are human. This may take a few seconds." During this verification, you may be required to solve a captcha. Previously, they used reCAPTCHA, but now they use their own Turnstile captcha.

Once captcha verification is passed, the browser receives a unique token in a cookie named cf_clearance. Your browser will use this token to request the website’s pages from the Cloudflare proxy. If this token expires, or if Cloudflare internally decides that the token is behaving like one usually used by bots, it will invalidate the token and you’ll see the verification captcha again.

Our method will help you obtain this token using an automated browser session. Note that this method does not reveal the website’s true IP address - that information is known only to Cloudflare and the website administrators. And this is good!

Here's how to do that with NodeJS and Playwright:

// Install packages
// npx install playwright @antiadmin/anticaptchaofficial
import { chromium } from "playwright";
import ac from "@antiadmin/anticaptchaofficial";

// Specify the target website address
const websiteBehindCloudFlare = 'https://yourwebsite.com';

// Set your Anti-Captcha API key here:
ac.setAPIKey('API_KEY_HERE');
ac.setSoftId(0);

let browser = null;
let page = null;


(async () => {

    // Opening the browser
    try {
        console.log('Opening browser ..');
        browser = await chromium.launch({ headless: false });
        console.log('Creating new page ..');
        page = await browser.newPage();
    } catch (e) {
        console.log("Could not open browser: "+e.toString());
        return;
    }


    let params = null;

    try {

        // Doing several attempts to inject our code
        while (!params) {

            console.log('Navigating to the page')
            await page.goto(websiteBehindCloudFlare);

            console.log('Injecting our proxy code to replace window.turnstile');
            await page.evaluate(() => {
                window.turnstile = new Proxy(window.turnstile, {
                  get(target, prop) {
                    if (prop === "render") {
                      return function (a, b) {
                        const p = {
                          websiteURL: window.location.href,
                          websiteKey: b.sitekey,
                          action: b.action,
                          cData: b.cData,
                          chlPageData: b.chlPageData,
                          userAgent: navigator.userAgent,
                        };

                        // saving params in window.params
                        window.params = p;

                        // assigning callback to a variable
                        window.cfCallback = b.callback

                        // calling original render function
                        return target.render.apply(this, arguments);
                      };
                    }
                    return target[prop];
                  },
                });
            });

          console.log('Getting params');
          params = await page.evaluate(() => {
            return new Promise((resolve) => {
              setTimeout(() => resolve(window.params || null), 5000);
            });
          });

          if (!params) {
            console.log('Retrying..');
            await delay(3000);
          }
        }

        console.log("Extracted Turnstile Params:", params);

        console.log('Solving Turnstile captcha with Anti-Captcha')
        const token = await ac.solveTurnstileProxyless(websiteBehindCloudFlare, params.websiteKey, params.action, params.cData, params.chlPageData);

        // Running Cloudflare's callback function we previously assigned to window.cfCallback
        await page.evaluate((token) => {
            window.cfCallback(token)
        }, token);

        console.log('Waiting for redirects to finish')
        await delay(5000);

        // Get all cookies for current page
        const cookies = await page.context().cookies();
        // Find cf_clearance
        const cf_clearance = cookies.filter(c => c.name === 'cf_clearance');

        // Output cookies
        console.log('Cookies:', cookies);
        console.log('cf_clearance:', cf_clearance);



    } catch (e) {
      console.error('Could not inject proxy code:', e);
    }

    // close browser when needed
    // await browser.close();


})();

function delay(time) {
   return new Promise(function(resolve) {
       setTimeout(resolve, time)
   });
}

What we are doing in our code:

1. Open browser window and navigate to the Cloudflare verification page.
2. Replace Turnstile's render function with our proxy function, where we catch the initialization parameters as well as captcha completion callback.
3. Send initialization parameters to Anti-Captcha API. Human workers solve this captcha for you and provide you with a token.
4. We call the previously saved callback function with the Turnstile token as a parameter.
5. Cloudflare internally verifies this token, sets cookie to our browser and reloads the page.
6. With that cf_clearance browser fetches website's contents from Cloudflare proxy.