The Trust Boundary — Client-Side vs Server-Side Security
Walk me through what you built for Volcanic Pantheon. Before we look at any problems, let's acknowledge the good decisions:
crypto.subtle.digest('SHA-256', ...) to hash the input before comparing it.protect.js file that runs on each protected page. That's good modular thinking.Here's the core of your authentication system:
const PASSWORD_HASH = "299957fa31b8e7784f4f7972436d9d4db8667f1bb7859faa4ec64d45c98861fb";
async function checkPassword() {
const input = document.getElementById("password").value;
const data = new TextEncoder().encode(input);
const buffer = await crypto.subtle.digest("SHA-256", data);
const hash = [...new Uint8Array(buffer)]
.map(b => b.toString(16).padStart(2, "0"))
.join("");
if (hash === PASSWORD_HASH) {
localStorage.setItem("authenticated", "true");
window.location.href = "Main/HomePage.html";
} else {
// Show error and shake animation
}
}
(function() {
if (localStorage.getItem("authenticated") !== "true") {
window.location.href = "../login.html";
}
})();
Take a moment. Look at protect.js. What does it check? Where does that value come from? Who controls it?
Let's do this together on your actual site right now:
1. Open Volcanic Pantheon in the browser
2. Try to visit a protected page — get redirected to login (expected)
3. Open DevTools (F12) → Console
4. Type: localStorage.setItem("authenticated", "true")
5. Press Enter. Refresh the page.
6. Full access granted. No password needed.
Even if you somehow fixed the localStorage bypass, there's another problem:
299957fa... sitting right there in auth.jsprotect.js script tag from the DOM, or set a breakpoint and skip the checkThis is not a flaw in your coding. Your code is actually well-written. The problem is where the code runs. This is a fundamental architectural problem — and that's what we're here to understand today.
Remember the AAA Model from last lesson? Let's apply it to your website. I want you to lead this — fill in the table together:
| AAA Component | What Volcanic Pantheon Does | What It Should Do |
|---|---|---|
| Authentication "Who are you?" |
??? | ??? |
| Authorisation "What can you do?" |
??? | ??? |
| Accountability "What did you do?" |
??? | ??? |
Current: SHA-256 hash comparison in JavaScript. Single shared password for everyone. No usernames — there is no identity, just a shared secret. No MFA.
Should be: Server verifies identity. Each user has their own credentials. Password is never sent to or stored on the client.
Current: Binary — you're either "authenticated" (see everything) or not (see nothing). No roles, no permissions.
Should be: Different users see different content. Role-based access control (RBAC) — some pages public, some require login, some admin-only.
Current: Nothing. Zero logging. No record of who accessed what, when, or from where.
Should be: Login events logged with timestamps and IP addresses. Page access tracked. Failed login attempts recorded.
Summary: Volcanic Pantheon has weak authentication (shared secret, client-side), no authorisation (all-or-nothing), and zero accountability (no logs). This AAA analysis is exactly what the HSC wants you to do in exam scenarios.
The fundamental principle: Everything the browser receives, the user controls.
Let's look at every tool an attacker has, using your site as the example:
Everything in HTML, CSS, and JavaScript is delivered to the browser in full. "View Source" shows it all. Minification (making code smaller) just makes it harder to read, not impossible.
Your SHA-256 hash, your protect.js logic, your page structure — all visible to anyone.
When you made your GitHub repo private, did that stop people from seeing your source code in the browser?
Every browser ships with powerful developer tools that give the user full control:
localStorage.setItem(...))// Attack 1: Bypass authentication
localStorage.setItem("authenticated", "true");
// Attack 2: If a tab was hidden with CSS
document.querySelector('.hidden-tab').style.display = 'block';
// Attack 3: If a JS variable controlled access
window.isAdmin = true;
// Attack 4: Remove the protection script entirely
document.querySelector('script[src="protect.js"]').remove();
Even if you added an API call to "check" the password, an attacker could intercept the response and change it. Browser extensions can modify request and response headers. This is why the check must happen server-side where the user cannot interfere.
Let's contrast your approach with how real authentication works:
CLIENT-SIDE AUTH (Your current approach):
=========================================
Browser has: password hash, check logic, ALL page content
Attacker can: see hash, skip check, access content directly
[Browser] ──── loads protect.js ──── checks localStorage ──── shows/hides page
▲ ▲
│ │
Everything happens HERE. Can be bypassed HERE.
The user controls all of this.
SERVER-SIDE AUTH (Proper approach):
====================================
Browser has: login form. That's it.
Attacker can: submit guesses. That's it.
[Browser] ──── sends password ────▶ [SERVER checks hash] ──── sends page content
│
Attacker CANNOT
reach this code.
It runs on YOUR
server, not their
browser.
The key insight: With server-side auth, the protected page content literally does not exist in the browser until the server decides to send it. There is nothing to bypass because there is nothing there yet.
| What You Did | Why It Feels Secure | Why It Isn't |
|---|---|---|
| Used SHA-256 hash | Hash can't be reversed | Attacker doesn't need to reverse it — just bypass the check entirely |
| Made GitHub repo private | Source code hidden on GitHub | Source code still fully visible in the browser via View Source |
| Changed hosting platform (Netlify → Vercel) | Different hosting, new URL | Same client-side architecture, same vulnerability |
| Created protect.js on every page | Consistent protection | All pages load full content first, then check — content can be intercepted |
Every fix you tried was still on the client side. It's like putting a stronger lock on a glass door — the lock is great, but the attacker can see through the glass and go around it.
You're already on Vercel, so you have real options. Here are three approaches, from simplest to most powerful:
Create a middleware.js file in your project root:
export default function middleware(request) {
const authHeader = request.headers.get('authorization');
if (authHeader && authHeader.startsWith('Basic ')) {
const encoded = authHeader.slice(6);
const decoded = atob(encoded);
const [username, password] = decoded.split(':');
if (username === 'blake' && password === 'securepassword123') {
return; // credentials correct — let the request through
}
}
return new Response('Authentication Required', {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="Volcanic Pantheon"',
},
});
}
export const config = {
matcher: ['/main/:path*'], // only protect /main/ pages
};
This is server-side. Vercel runs this middleware before serving any page. The page content never reaches the browser without authentication. No JavaScript, no localStorage, no bypass.
Remember encryption from Lesson 7? StatiCrypt uses it to protect static pages:
For when you eventually build anything with an API or server:
╔══════════════════════════════════════════════════════════╗
║ ║
║ "NEVER TRUST THE CLIENT." ║
║ ║
╚══════════════════════════════════════════════════════════╝
TRUST BOUNDARY
══════════════════════════════════════════════════════════
Client (Browser) │ Server
───────────────── │ ──────────────────
• User controls │ • You control
• Can be inspected │ • Hidden from user
• Can be modified │ • Tamper-proof
• NEVER trust │ • ALWAYS verify here
│
← Attacker's territory → │ ← Your territory →
localStorage.setItem("authenticated", "true") to track login status. Explain TWO reasons why this is an insecure approach.middleware.js file that checks credentials server-side before serving the page. Explain why approach B provides stronger security than approach A.Reason 1: localStorage is accessible from the browser's DevTools console, meaning any user can set the value to "true" without knowing the password, completely bypassing the authentication system.
Reason 2: localStorage data persists indefinitely and is not encrypted, so the authentication state can be read or modified by any JavaScript running on the same origin, including potentially malicious scripts.
Client-side validation runs in the user's browser, which the user has full control over. They can disable JavaScript, modify the validation code via DevTools, or send requests directly to the server bypassing the browser entirely.
An attacker can intercept and modify network requests between the browser and server, sending malicious data even if client-side validation would normally block it.
Therefore, server-side validation is essential because the server is the only component the developer controls. Client-side validation improves user experience (fast feedback) but must always be complemented by server-side validation for security.
Approach A (JavaScript hash check) runs entirely in the browser. The protected page content is delivered to the browser before the password is checked, and the check itself can be bypassed by manipulating localStorage, removing the script tag via DevTools, or setting a breakpoint to skip the redirect. The password hash is also visible in the source code.
Approach B (Vercel middleware) is enforced server-side. Vercel runs the middleware before sending any page content. If the credentials are incorrect, the page is never transmitted to the browser. There is nothing on the client to bypass because the protected content literally does not exist in the browser until the server authorises the request.
The key principle is "never trust the client" — any security check that runs in the browser can be circumvented by the user, whereas server-side checks run in an environment the attacker cannot access.
Next lesson (Lesson 9): Web Vulnerabilities — a deep dive into specific attack types: hardcoded secrets, security through obscurity, shoulder surfing and social engineering, and XSS (Cross-Site Scripting). We'll also cover Kerckhoffs's principle and do five HSC-style exam questions together.
Task 1: Fix the Server-Side Protection
Add a middleware.js file to your Volcanic Pantheon project and deploy it to Vercel. Your middleware should:
WWW-Authenticate header if credentials are missing or wrongThe code from today's lesson is your starting point — adapt it to match your site's folder structure.
Task 2: Add Authorisation
Right now your site is all-or-nothing. Add a second layer so that different pages require different credentials (or at minimum, restrict access differently based on the URL path). For example:
Use the matcher config in your middleware to target the right paths.
You can have multiple if checks in your middleware based on request.url, or use separate matchers. The key is that the logic runs on Vercel's servers, not in the browser.