Skip to content

Instantly share code, notes, and snippets.

@arizz96
Created July 20, 2024 11:28
Show Gist options
  • Save arizz96/9478d963e5ae20346fba8e591a0fcbd2 to your computer and use it in GitHub Desktop.
Save arizz96/9478d963e5ae20346fba8e591a0fcbd2 to your computer and use it in GitHub Desktop.
Basic authentication middleware for Cloudflare workers
/**
* Basic authentication middleware for Cloudflare workers inspired by official guide:
* @see https://developers.cloudflare.com/workers/examples/basic-auth/
*
*/
const encoder = new TextEncoder();
/**
* Protect against timing attacks by safely comparing values using `timingSafeEqual`.
* Refer to https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#timingsafeequal for more details
* @param {string} a
* @param {string} b
* @returns {boolean}
*/
function timingSafeEqual(a, b) {
const aBytes = encoder.encode(a);
const bBytes = encoder.encode(b);
if (aBytes.byteLength !== bBytes.byteLength) {
// Strings must be the same length in order to compare
// with crypto.subtle.timingSafeEqual
return false;
}
return crypto.subtle.timingSafeEqual(aBytes, bBytes);
}
function decodeBase64(base64) {
// Decode the Base64 string to a binary string
const binaryString = atob(base64);
// Convert the binary string to a UTF-8 string
const utf8String = decodeURIComponent(escape(binaryString));
return utf8String;
}
function requireBasicAuthentication() {
return new Response("You need to login.", {
status: 401,
headers: {
// Prompts the user for credentials.
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"',
},
});
}
function authentication(context) {
// You will need an admin password. This should be
// attached to your Worker as an encrypted secret.
// Refer to https://developers.cloudflare.com/workers/configuration/secrets/
const BASIC_USER = context.env.BASIC_USER ?? "admin";
const BASIC_PASS = context.env.BASIC_PASS ?? "password";
// The "Authorization" header is sent when authenticated.
const authorization = context.request.headers.get("Authorization");
if (!authorization) {
return requireBasicAuthentication();
}
const [scheme, encoded] = authorization.split(" ");
// The Authorization header must start with Basic, followed by a space.
if (!encoded || scheme !== "Basic") {
return requireBasicAuthentication();
}
const credentials = decodeBase64(encoded);
// The username & password are split by the first colon.
//=> example: "username:password"
const index = credentials.indexOf(":");
const user = credentials.substring(0, index);
const pass = credentials.substring(index + 1);
if (!timingSafeEqual(BASIC_USER, user) || !timingSafeEqual(BASIC_PASS, pass)) {
return requireBasicAuthentication();
}
return context.next();
}
export const onRequest = [authentication];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment