Authentication (Cloudflare)

a sign-in in front of a wiki published on Cloudflare

This page is a draft. The owner has not approved what it says this part of the system is for, so read it as a proposal rather than as the wiki.

A wiki published on Cloudflare Pages can ask for a sign-in before it shows any page, in one of two ways: Cloudflare Access, which emails each allowed reader a sign-in code, or a Pages Function that asks the browser for a shared user name and password.[1][2] Cloudflare recommends Access for a production sign-in, and a password prompt is safe only over HTTPS, because it sends the password unencrypted.[3] Publishing the wiki is described on Deployment (Cloudflare).

wiki publish writes a markdown copy beside each page and an llms.txt listing them, and each holds the wiki's text, so a sign-in guards those files as well as the pages.[4]

Cloudflare Access

Access lets in only the email addresses its policy allows, and each reader signs in with a code sent to that address, with no identity provider to connect.[1][5] The code expires ten minutes after the reader asks for it.[1] A policy's Allow action admits the readers its rules match: named addresses under Emails, or a whole domain under Emails ending in.[5] Access protects a whole address, so the markdown copies and llms.txt sit behind it with the pages.[6][4]

Each address the wiki is served from is protected on its own.[7][8]

Address Setting
Preview deployments the project's Settings, then Enable access policy[7]
PROJECT.pages.dev edit the policy that Enable access policy creates, delete the * from its Subdomain field and save, then enable the access policy again[8]
Custom domain under Zero Trust, Access controls, then Applications: Create new application, Self-hosted and private, Add public hostname, and the domain[9]

A custom domain without its own Access policy shows a sign-in that does not work.[9] The Zero Trust Free plan asks for a payment method when the account is set up, and does not charge it.[10]

Password prompt

A Pages Function at functions/_middleware.js runs in front of every request, static files included, and passes an accepted request on to the page.[2] The functions folder sits at the root of the repository Cloudflare builds, not inside _site.[11] The user name and password are Pages secrets, added with Encrypt selected under the project's Settings, then Variables and Secrets, before the deployment that reads them.[12]

The middleware below adapts Cloudflare's Basic Authentication example to Pages: it reads WIKI_USER and WIKI_PASSWORD from those secrets, compares both in constant time, and answers a missing or wrong password with the 401 that makes the browser ask again.[3][12]

// functions/_middleware.js
const encoder = new TextEncoder();

// Compares in constant time, so the time taken reveals nothing about the password.
function timingSafeEqual(a, b) {
  const aBytes = encoder.encode(a);
  const bBytes = encoder.encode(b);
  if (aBytes.byteLength !== bBytes.byteLength) {
    return !crypto.subtle.timingSafeEqual(aBytes, aBytes);
  }
  return crypto.subtle.timingSafeEqual(aBytes, bBytes);
}

function signIn() {
  return new Response("Sign in to read the wiki.", {
    status: 401,
    headers: { "WWW-Authenticate": 'Basic realm="wiki", charset="UTF-8"' },
  });
}

export async function onRequest(context) {
  const { WIKI_USER, WIKI_PASSWORD } = context.env;
  if (!WIKI_USER || !WIKI_PASSWORD) {
    return new Response("WIKI_USER and WIKI_PASSWORD are not set.", { status: 500 });
  }
  const [scheme, encoded] = (context.request.headers.get("Authorization") || "").split(" ");
  if (scheme !== "Basic" || !encoded) {
    return signIn();
  }
  let credentials;
  try {
    credentials = atob(encoded);
  } catch {
    return signIn();
  }
  const colon = credentials.indexOf(":");
  const user = credentials.substring(0, colon);
  const password = credentials.substring(colon + 1);
  if (colon < 0 || !timingSafeEqual(WIKI_USER, user) || !timingSafeEqual(WIKI_PASSWORD, password)) {
    return signIn();
  }
  return context.next();
}

External links

Last updated 14 September 2026Last audited never430 wordsabout 2 minutes to read12 sources cited0 claims with no source