Password protect your (Vercel) site with Cloudflare Workers

UPDATE: Password protected deployments are now FREE for all plans on Vercel! You can learn more by reading the announcement.


I am working on a new project where I already setup the deployment pipeline. The app has a backend and a frontend. The frontend is built with Next.js and therefore I deployed it to Vercel. Because the state of the current project is not publishable I want to protect the access with a password. Vercel offers this option, but you need to have the Pro plan that costs 20$ per month.

Vercel Pricing

Alright that would not be too bad, but wait why is there a small info icon next to password protection 🤔

Vercel Password Protection Pricing

This is way out of my budget and I don’t understand why Vercel is selling this feature with such a high price tag. Netlify for example offers password protection too, but they have it included in the Pro plan for 19$ / month and no additional costs.

Netlify Pricing

So I thought maybe I can add password protection with something else. And yes there is a solution: Cloudflare Workers!

To learn about Cloudflare Workers (CW) I would suggest you look at the docs. Basically, they are similar to serverless functions, but without the downsides.

Let’s protect our site with Cloudflare Workers

First of all your domain needs to be managed by Cloudflare to be used together with CW. If you have this working it is just a matter of minutes to have your site protected.

To create a CW you need to go to your Dashboard and select Workers on the right side of the navigation.

Cloudflare Workers link on the dashboard

Next, you click Create a Worker and you should be shown the editor where we can write our code.

Of course, I was not the first one that had the idea to use CW to protect their site with it. After a quick Google search, I found this Repository on GitHub by dommmel.

Thanks to his efforts in creating this worker it was as easy as copying the contents of the index.js file and past it into the editor.

The only things you need to change now are the NAME, and PASS at the top. Between lines 76 and 77 you paste the following code:

const [protocol, , domain] = request.url.split('/');

if (request.url.startsWith(`${protocol}//${domain}/.well-known/acme-challenge`)) {
  return fetch(request);
}

This will allow Vercel to still be able to generate Let’s Encrypt certificates.

After that your code should look like this with your custom username and password:

const NAME = 'CUSTOM_USER';
const PASS = 'SUPER_SECRET_PASSWORD';

/**
 * RegExp for basic auth credentials
 *
 * credentials = auth-scheme 1*SP token68
 * auth-scheme = "Basic" ; case insensitive
 * token68     = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
 */

const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/;

/**
 * RegExp for basic auth user/pass
 *
 * user-pass   = userid ":" password
 * userid      = *<TEXT excluding ":">
 * password    = *TEXT
 */

const USER_PASS_REGEXP = /^([^:]*):(.*)$/;

/**
 * Object to represent user credentials.
 */

const Credentials = function (name, pass) {
  this.name = name;
  this.pass = pass;
};

/**
 * Parse basic auth to object.
 */

const parseAuthHeader = function (string) {
  if (typeof string !== 'string') {
    return undefined;
  }

  // parse header
  const match = CREDENTIALS_REGEXP.exec(string);

  if (!match) {
    return undefined;
  }

  // decode user pass
  const userPass = USER_PASS_REGEXP.exec(atob(match[1]));

  if (!userPass) {
    return undefined;
  }

  // return credentials object
  return new Credentials(userPass[1], userPass[2]);
};

const unauthorizedResponse = function (body) {
  return new Response(body, {
    status: 401,
    headers: {
      'WWW-Authenticate': 'Basic realm="User Visible Realm"'
    }
  });
};

/**
 * Handle request
 */

async function handle(request) {
  const [protocol, , domain] = request.url.split('/');

  if (request.url.startsWith(`${protocol}//${domain}/.well-known/acme-challenge`)) {
    return fetch(request);
  }

  const credentials = parseAuthHeader(request.headers.get('Authorization'));
  if (!credentials || credentials.name !== NAME || credentials.pass !== PASS) {
    return unauthorizedResponse('Unauthorized');
  } else {
    return fetch(request);
  }
}

addEventListener('fetch', (event) => {
  event.respondWith(handle(event.request));
});

Now that we have our worker finished we need can change the name at the top left and with Save and Deploy we can (duh..) deploy it!

The worker is now ready to be used and we can assign it to a custom domain. On Cloudflare open up your domain and go to the Workers tab.

Cloudflare Workers tab

Click Add Route and type the text from the placeholder into the input field. If your domain is example.com add *example.com/*. In the select box, select the newly created CF. This instructs Cloudflare to use this worker whenever the URL matches the route we defined.

On the DNS tab make sure the domain is proxied through Cloudflare (orange cloud), otherwise, the CW would not work.

Make it work with Vercel

Now that the Cloudflare Worker is set up and running, we still need to change a few settings in Cloudflare to make it work nicely with Vercel.

The first thing we check is on the SSL/TLS tab in Cloudflare. Make sure you have it set to Full (strict) otherwise you will get a Too many redirects error and can’t access your site.

Cloudflare SSL/TLS settings

Finally, we need to tell Cloudflare that the route /.well-known/* can be accessed with HTTP instead of HTTPS. This is used by Vercel to create a Let’s Encrypt certificate.

To do this we need to go to the Page Rules tab on Cloudflare and create a new Page Rule. Enter in the first input field /.well-known/* and select in the select field SSL and Off. With Save and Deploy your rule will be applied.

Add Clouflare Page Rule to disable HTTPS on /.well-known/*

We are finally done 🥳

Now your page should be protected using Basic HTTP authentication with the username and password you provided in the Cloudflare Worker.

I have set up a simple demo site where I protected an HTML site hosted on Vercel using the Cloudflare Worker we just created.

The site is accessible at https://protected-site-example.phiilu.com/

  • Username: phiilu
  • Password: shiba-inus-are-awesome!

Conclusion

Thanks to Cloudflare Workers we can simply add password protection to our sites!

In my opinion, such a feature should not cost 150$ a month and I hope that Vercel will rethink this choice and adds this as an included feature in the Pro plan.


Want blog post updates? Sign up for my newsletter.