Back to blog
Aug 25, 2024
5 min read

Cloudflare Worker Middleware: Username+Password Authentication

Use Cloudflare Worker to deploy it as a middleware, for simple authentication (username+passwrod). Below are mutiple versions you can choose. Code was modfied by Claude 3.5 if i remember correctly, original code can be found in Credits.

I used to use the Hono.js middleware but sometimes use this javascript version is actually faster and less clusterf**k.

Before you use: Make sure you add all urls as Routes in your Worker dashboard. Otherwise it won’t work. The paths added in Routes are protected.

Cloudflare Worker 1 - Standard

This is the standard version which supports multiple user&password for authentication, I used this in for early file downloading site. recommended

1
const credentials = [
2
{ username: 'user0', password: 'password0' },
3
{ username: 'user1', password: 'password1' },
4
{ username: 'user2', password: 'password2' },
5
// Add more users here...
6
];
7
8
const REALM = 'Secure Area';
9
10
addEventListener('fetch', (event) => {
11
event.respondWith(handleRequest(event.request));
12
});
13
14
async function handleRequest(request) {
15
const authorization = request.headers.get('authorization');
16
if (!request.headers.has('authorization')) {
17
return getUnauthorizedResponse('Provide Username and Password to access this page.');
18
}
19
const parsedCredentials = parseCredentials(authorization);
20
const validCredential = credentials.find((credential) => {
21
return credential.username === parsedCredentials[0] && credential.password === parsedCredentials[1];
22
});
23
if (!validCredential) {
24
return getUnauthorizedResponse('The User Name and Password combination you have entered is invalid.');
25
}
26
return await fetch(request);
27
}
28
29
function parseCredentials(authorization) {
30
const parts = authorization.split(' ');
31
const plainAuth = atob(parts[1]);
32
const credentials = plainAuth.split(':');
33
return credentials;
34
}
35
36
function getUnauthorizedResponse(message) {
37
let response = new Response(message, {
38
status: 401,
39
});
40
response.headers.set('WWW-Authenticate', `Basic realm="${REALM}"`);
41
return response;
42
}

Example:

I will give this Worker my subdomain “hello.talesofstar.com”, then I can add the Route “hello.talesofstar.com/*”, then all requests to this site will need authentication.

Users defined in code will use their username+password to finish the authentication, very like logging in a website.

Is this secure? HTTP Basic Authentication is secure on severless environment from my understanding (also using experience). But just let you know, there is no rate limiting in this middleware, so tecnically an attacker can try accessing many times however they want.

I have moved to new framework Hono.js. I used it’s middleware but envolved many versions with more advanced and secure setup. However Hono.js needs more code (tho it’s already super lit and fast), sometimes I just prefer pure javascript like this to build a middleware to test out new things on new sub domains.

Cloudflare Worker 2 - Specific Paths

Well, this version supports multiple users but can specific multiple different urls. More control but more “unnecessary” sometimes.

1
const credentials = [
2
{ username: 'u1', password: 'p1', urls: ['https://talesofstar.com/1', 'https://talesofstar.com/1x'] },
3
{ username: 'u2', password: 'p2', urls: ['https://talesofstar.com/2', 'https://talesofstar.com/2x'] },
4
5
// Add more users here...
6
];
7
8
const REALM = 'Secure Area';
9
10
addEventListener('fetch', (event) => {
11
event.respondWith(handleRequest(event.request));
12
});
13
14
async function handleRequest(request) {
15
const authorization = request.headers.get('authorization');
16
if (!request.headers.has('authorization')) {
17
return getUnauthorizedResponse('Provide User Name and Password to access this page.');
18
}
19
const parsedCredentials = parseCredentials(authorization);
20
const validCredential = credentials.find((credential) => {
21
return credential.username === parsedCredentials[0] && credential.password === parsedCredentials[1] && credential.urls.includes(request.url);
22
});
23
if (!validCredential) {
24
return getUnauthorizedResponse('The User Name and Password combination you have entered is invalid or do not have access to the requested URL.');
25
}
26
return await fetch(request);
27
}
28
29
function parseCredentials(authorization) {
30
const parts = authorization.split(' ');
31
const plainAuth = atob(parts[1]);
32
const credentials = plainAuth.split(':');
33
return credentials;
34
}
35
36
function getUnauthorizedResponse(message) {
37
let response = new Response(message, {
38
status: 401,
39
});
40
response.headers.set('WWW-Authenticate', `Basic realm="${REALM}"`);
41
return response;
42
}

Note:

Add thse paths to Routes in your Worker settings, just one by one if you have multiple.

Credits

Original middleware code was created by @Max Ivanov (link in links section):

Max Ivanov’s code:

1
/**
2
* @param {string} USERNAME User name to access the page
3
* @param {string} PASSWORD Password to access the page
4
* @param {string} REALM A name of an area (a page or a group of pages) to protect.
5
* Some browsers may show "Enter user name and password to access REALM"
6
*/
7
const USERNAME = 'demouser'
8
const PASSWORD = 'demopassword'
9
const REALM = 'Secure Area'
10
11
addEventListener('fetch', (event) => {
12
event.respondWith(handleRequest(event.request))
13
})
14
15
async function handleRequest(request) {
16
const authorization = request.headers.get('authorization')
17
if (!request.headers.has('authorization')) {
18
return getUnauthorizedResponse(
19
'Provide User Name and Password to access this page.',
20
)
21
}
22
const credentials = parseCredentials(authorization)
23
if (credentials[0] !== USERNAME || credentials[1] !== PASSWORD) {
24
return getUnauthorizedResponse(
25
'The User Name and Password combination you have entered is invalid.',
26
)
27
}
28
return await fetch(request)
29
}
30
31
/**
32
* Break down base64 encoded authorization string into plain-text username and password
33
* @param {string} authorization
34
* @returns {string[]}
35
*/
36
function parseCredentials(authorization) {
37
const parts = authorization.split(' ')
38
const plainAuth = atob(parts[1])
39
const credentials = plainAuth.split(':')
40
return credentials
41
}
42
43
/**
44
* Helper funtion to generate Response object
45
* @param {string} message
46
* @returns {Response}
47
*/
48
function getUnauthorizedResponse(message) {
49
let response = new Response(message, {
50
status: 401,
51
})
52
response.headers.set('WWW-Authenticate', `Basic realm="${REALM}"`)
53
return response
54
}