Cloudflare Docs
Workers
Visit Workers on GitHub
Set theme to dark (⇧+D)

HTTP Basic Authentication

Shows how to restrict access using the HTTP Basic schema.
/**
* 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
*
* A user-id containing a colon (":") character is invalid, as the
* first colon in a user-pass string separates user and password.
*/
export default {
async fetch(request) {
const BASIC_USER = "admin";
const BASIC_PASS = "admin";
/**
* Throws exception on verification failure.
* @param {string} user
* @param {string} pass
* @throws {UnauthorizedException}
*/
async function verifyCredentials(user, pass) {
if (BASIC_USER !== user) {
throw new UnauthorizedException("Invalid credentials.");
}
if (BASIC_PASS !== pass) {
throw new UnauthorizedException("Invalid credentials.");
}
}
/**
* Parse HTTP Basic Authorization value.
* @param {Request} request
* @throws {BadRequestException}
* @returns {{ user: string, pass: string }}
*/
async 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 BadRequestException("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 BadRequestException("Invalid authorization value.");
}
return {
user: decoded.substring(0, index),
pass: decoded.substring(index + 1),
};
}
async function UnauthorizedException(reason) {
this.status = 401;
this.statusText = "Unauthorized";
this.reason = reason;
}
async function BadRequestException(reason) {
this.status = 400;
this.statusText = "Bad Request";
this.reason = reason;
}
const { protocol, pathname } = new URL(request.url);
// In the case of a Basic authentication, the exchange MUST happen over an HTTPS (TLS) connection to be secure.
if (
"https:" !== protocol ||
"https" !== request.headers.get("x-forwarded-proto")
) {
throw new BadRequestException("Please use a HTTPS connection.");
}
switch (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.
if (request.headers.has("Authorization")) {
// Throws exception when authorization fails.
const { user, pass } = basicAuthentication(request);
verifyCredentials(user, pass);
// Only returns this response when no exception is thrown.
return new Response("You have private access.", {
status: 200,
headers: {
"Cache-Control": "no-store",
},
});
}
// Not authenticated.
return new Response("You need to login.", {
status: 401,
headers: {
// Prompts the user for credentials.
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"',
},
});
}
case "/favicon.ico":
case "/robots.txt":
return new Response(null, { status: 204 });
}
return new Response("Not Found.", { status: 404 });
},
};
const handler: ExportedHandler = {
async fetch(request: Request) {
const BASIC_USER = "admin";
const BASIC_PASS = "admin";
/**
* Throws exception on verification failure.
* @param {string} user
* @param {string} pass
* @throws {UnauthorizedException}
*/
async function verifyCredentials(user, pass) {
if (BASIC_USER !== user) {
throw new UnauthorizedException("Invalid credentials.");
}
if (BASIC_PASS !== pass) {
throw new UnauthorizedException("Invalid credentials.");
}
}
/**
* Parse HTTP Basic Authorization value.
* @param {Request} request
* @throws {BadRequestException}
* @returns {{ user: string, pass: string }}
*/
async 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 BadRequestException("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 BadRequestException("Invalid authorization value.");
}
return {
user: decoded.substring(0, index),
pass: decoded.substring(index + 1),
};
}
async function UnauthorizedException(reason) {
this.status = 401;
this.statusText = "Unauthorized";
this.reason = reason;
}
async function BadRequestException(reason) {
this.status = 400;
this.statusText = "Bad Request";
this.reason = reason;
}
const { protocol, pathname } = new URL(request.url);
// In the case of a Basic authentication, the exchange MUST happen over an HTTPS (TLS) connection to be secure.
if (
"https:" !== protocol ||
"https" !== request.headers.get("x-forwarded-proto")
) {
throw new BadRequestException("Please use a HTTPS connection.");
}
switch (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.
if (request.headers.has("Authorization")) {
// Throws exception when authorization fails.
const { user, pass } = basicAuthentication(request);
verifyCredentials(user, pass);
// Only returns this response when no exception is thrown.
return new Response("You have private access.", {
status: 200,
headers: {
"Cache-Control": "no-store",
},
});
}
// Not authenticated.
return new Response("You need to login.", {
status: 401,
headers: {
// Prompts the user for credentials.
"WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"',
},
});
}
case "/favicon.ico":
case "/robots.txt":
return new Response(null, { status: 204 });
}
return new Response("Not Found.", { status: 404 });
},
};
export default handler;