Created
July 20, 2024 11:28
-
-
Save arizz96/9478d963e5ae20346fba8e591a0fcbd2 to your computer and use it in GitHub Desktop.
Basic authentication middleware for Cloudflare workers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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