Created
December 5, 2023 19:21
-
-
Save asimpletune/35171f5cd1d017665d6511b48d9966f5 to your computer and use it in GitHub Desktop.
Cloudflare Pages' Basic Auth Middleware
This file contains 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
async function errorHandling(context) { | |
try { | |
return await context.next(); | |
} catch (err) { | |
return new Response(`${err.message}\n${err.stack}`, { status: 500 }); | |
} | |
} | |
/** | |
* Parse HTTP Basic Authorization value. | |
* @param {Request} request | |
* @throws {Error} | |
* @returns {{ user: string, pass: string }} | |
*/ | |
function basicAuthentication(request) { | |
const Authorization = request.headers.get("Authorization"); | |
const [scheme, encoded] = Authorization.split(" "); | |
// The Authorization header must start with Basic, followed by a space. | |
if (!encoded || scheme !== "Basic") { | |
throw new Error("Malformed authorization header."); | |
} | |
// Decodes the base64 value and performs unicode normalization. | |
// @see https://datatracker.ietf.org/doc/html/rfc7613#section-3.3.2 (and #section-4.2.2) | |
// @see https://dev.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/normalize | |
const buffer = Uint8Array.from(atob(encoded), (character) => | |
character.charCodeAt(0) | |
); | |
const decoded = new TextDecoder().decode(buffer).normalize(); | |
// The username & password are split by the first colon. | |
//=> example: "username:password" | |
const index = decoded.indexOf(":"); | |
// The user & password are split by the first colon and MUST NOT contain control characters. | |
// @see https://tools.ietf.org/html/rfc5234#appendix-B.1 (=> "CTL = %x00-1F / %x7F") | |
if (index === -1 || /[\0-\x1F\x7F]/.test(decoded)) { | |
throw new Error("Invalid authorization value."); | |
} | |
return { | |
user: decoded.substring(0, index), | |
pass: decoded.substring(index + 1), | |
}; | |
} | |
async function authentication(context) { | |
// The "Authorization" header is sent when authenticated. | |
if (context.request.headers.has("Authorization")) { | |
// extract env vars | |
const BASIC_USER = context.env.BASIC_USER | |
const BASIC_PASS = context.env.BASIC_PASS | |
// Throws exception when authorization fails. | |
const { user, pass } = basicAuthentication(context.request); | |
if (BASIC_USER !== user || BASIC_PASS !== pass) { | |
return new Response("Invalid credentials. Please login.", { | |
status: 401, | |
headers: { | |
// Prompts the user for credentials. | |
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"', | |
}, | |
}); | |
} | |
if (BASIC_USER === user && BASIC_PASS === pass) { | |
return context.next(); | |
} | |
} else { | |
// Not authenticated. | |
return new Response("You are not authenticated", { | |
status: 401, | |
headers: { | |
// Prompts the user for credentials. | |
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"', | |
}, | |
}); | |
} | |
} | |
export const onRequest = [authentication]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment