/**
* Shows how to restrict access using the HTTP Basic schema.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
* @see https://tools.ietf.org/html/rfc7617
*
*/
import { Buffer } from "node:buffer";
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);
// Do not return early when lengths differ — that leaks the secret's
// length through timing. Compare against self and negate instead.
if (aBytes.byteLength !== bBytes.byteLength) {
return !crypto.subtle.timingSafeEqual(aBytes, aBytes);
}
return crypto.subtle.timingSafeEqual(aBytes, bBytes);
}
export default {
/**
*
* @param {Request} request
* @param {{PASSWORD: string}} env
* @returns
*/
async fetch(request, env) {
const BASIC_USER = "admin";
// 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_PASS = env.PASSWORD ?? "password";
const url = new URL(request.url);
switch (url.pathname) {
case "/":
return new Response("Anyone can access the homepage.");
case "/logout":
// Invalidate the "Authorization" header by returning a HTTP 401.
// We do not send a "WWW-Authenticate" header, as this would trigger
// a popup in the browser, immediately asking for credentials again.
return new Response("Logged out.", { status: 401 });
case "/admin": {
// The "Authorization" header is sent when authenticated.
const authorization = request.headers.get("Authorization");
if (!authorization) {
return new Response("You need to login.", {
status: 401,
headers: {
// Prompts the user for credentials.
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"',
},
});
}
const [scheme, encoded] = authorization.split(" ");
// The Authorization header must start with Basic, followed by a space.
if (!encoded || scheme !== "Basic") {
return new Response("Malformed authorization header.", {
status: 400,
});
}
const credentials = Buffer.from(encoded, "base64").toString();
// 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 new Response("You need to login.", {
status: 401,
headers: {
// Prompts the user for credentials.
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"',
},
});
}
return new Response("🎉 You have private access!", {
status: 200,
headers: {
"Cache-Control": "no-store",
},
});
}
}
return new Response("Not Found.", { status: 404 });
},
};/**
* Shows how to restrict access using the HTTP Basic schema.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
* @see https://tools.ietf.org/html/rfc7617
*
*/
import { Buffer } from "node:buffer";
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
*/
function timingSafeEqual(a: string, b: string) {
const aBytes = encoder.encode(a);
const bBytes = encoder.encode(b);
// Do not return early when lengths differ — that leaks the secret's
// length through timing. Compare against self and negate instead.
if (aBytes.byteLength !== bBytes.byteLength) {
return !crypto.subtle.timingSafeEqual(aBytes, aBytes);
}
return crypto.subtle.timingSafeEqual(aBytes, bBytes);
}
interface Env {
PASSWORD: string;
}
export default {
async fetch(request, env): Promise<Response> {
const BASIC_USER = "admin";
// 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_PASS = env.PASSWORD ?? "password";
const url = new URL(request.url);
switch (url.pathname) {
case "/":
return new Response("Anyone can access the homepage.");
case "/logout":
// Invalidate the "Authorization" header by returning a HTTP 401.
// We do not send a "WWW-Authenticate" header, as this would trigger
// a popup in the browser, immediately asking for credentials again.
return new Response("Logged out.", { status: 401 });
case "/admin": {
// The "Authorization" header is sent when authenticated.
const authorization = request.headers.get("Authorization");
if (!authorization) {
return new Response("You need to login.", {
status: 401,
headers: {
// Prompts the user for credentials.
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"',
},
});
}
const [scheme, encoded] = authorization.split(" ");
// The Authorization header must start with Basic, followed by a space.
if (!encoded || scheme !== "Basic") {
return new Response("Malformed authorization header.", {
status: 400,
});
}
const credentials = Buffer.from(encoded, "base64").toString();
// The username and 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 new Response("You need to login.", {
status: 401,
headers: {
// Prompts the user for credentials.
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"',
},
});
}
return new Response("🎉 You have private access!", {
status: 200,
headers: {
"Cache-Control": "no-store",
},
});
}
}
return new Response("Not Found.", { status: 404 });
},
} satisfies ExportedHandler<Env>;use base64::prelude::*;
use worker::*;
#[event(fetch)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
let basic_user = "admin";
// 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/
let basic_pass = match env.secret("PASSWORD") {
Ok(s) => s.to_string(),
Err(_) => "password".to_string(),
};
let url = req.url()?;
match url.path() {
"/" => Response::ok("Anyone can access the homepage."),
// Invalidate the "Authorization" header by returning a HTTP 401.
// We do not send a "WWW-Authenticate" header, as this would trigger
// a popup in the browser, immediately asking for credentials again.
"/logout" => Response::error("Logged out.", 401),
"/admin" => {
// The "Authorization" header is sent when authenticated.
let authorization = req.headers().get("Authorization")?;
if authorization == None {
let mut headers = Headers::new();
// Prompts the user for credentials.
headers.set(
"WWW-Authenticate",
"Basic realm='my scope', charset='UTF-8'",
)?;
return Ok(Response::error("You need to login.", 401)?.with_headers(headers));
}
let authorization = authorization.unwrap();
let auth: Vec<&str> = authorization.split(" ").collect();
let scheme = auth[0];
let encoded = auth[1];
// The Authorization header must start with Basic, followed by a space.
if encoded == "" || scheme != "Basic" {
return Response::error("Malformed authorization header.", 400);
}
let buff = BASE64_STANDARD.decode(encoded).unwrap();
let credentials = String::from_utf8_lossy(&buff);
// The username & password are split by the first colon.
//=> example: "username:password"
let credentials: Vec<&str> = credentials.split(':').collect();
let user = credentials[0];
let pass = credentials[1];
if user != basic_user || pass != basic_pass {
let mut headers = Headers::new();
// Prompts the user for credentials.
headers.set(
"WWW-Authenticate",
"Basic realm='my scope', charset='UTF-8'",
)?;
return Ok(Response::error("You need to login.", 401)?.with_headers(headers));
}
let mut headers = Headers::new();
headers.set("Cache-Control", "no-store")?;
Ok(Response::ok("🎉 You have private access!")?.with_headers(headers))
}
_ => Response::error("Not Found.", 404),
}
}/**
* Shows how to restrict access using the HTTP Basic schema with Hono.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
* @see https://tools.ietf.org/html/rfc7617
*/
import { Hono } from "hono";
import { basicAuth } from "hono/basic-auth";
// Define environment interface
interface Env {
Bindings: {
USERNAME: string;
PASSWORD: string;
};
}
const app = new Hono<Env>();
// Public homepage - accessible to everyone
app.get("/", (c) => {
return c.text("Anyone can access the homepage.");
});
// Admin route - protected with Basic Auth
app.get(
"/admin",
async (c, next) => {
const auth = basicAuth({
username: c.env.USERNAME,
password: c.env.PASSWORD,
});
return await auth(c, next);
},
(c) => {
return c.text("🎉 You have private access!", 200, {
"Cache-Control": "no-store",
});
},
);
export default app;