Pular para o conteúdo

HTTP Basic Authentication

Shows how to restrict access using the HTTP Basic schema.

Atualizado em Ver como Markdown
/**
 * 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;