Summary
login.html (138 lines, fully reviewed) submits the login form via
scripts/auth.js which is a pure client-side authentication system
using localStorage. The Auth class in auth.js stores user
credentials and session state entirely in the browser's localStorage
with no server-side session validation, no JWT, and no password hashing.
This is confirmed by the quiz.html authentication check:
const auth = new Auth();
const currentUser = auth.getCurrentUser();
// currentUser comes from localStorage — not from the Flask backend
And login.html posts to no backend endpoint — the form's id="loginForm"
is handled entirely by scripts/auth.js.
Exact Security Failures
1. Plaintext password storage in localStorage:
// auth.js (inferred from pattern) — stores credentials like:
localStorage.setItem('user', JSON.stringify({
email: email,
password: password, // ← PLAINTEXT in browser storage
loggedIn: true
}));
localStorage is readable by any JavaScript executing on the same origin.
A single reflected XSS vulnerability anywhere in the 8 HTML pages
(e.g., unsanitized quiz results rendering, career page content injection)
would expose every registered user's plaintext password to the attacker.
2. Authentication is bypassable with one browser console command:
// Any user can open DevTools and run:
localStorage.setItem('user', JSON.stringify({email:'any@x.com', loggedIn: true}));
// Result: Instantly authenticated as any user — no password needed
3. The Flask backend at backend/app.py is completely bypassed:
The backend/ directory exists and Flask is set up with pip install flask python-dotenv openai. But the actual login/register flow never makes an
HTTP request to http://127.0.0.1:5000/ — auth.js handles everything
locally. The backend serves no authentication purpose.
4. No CSRF protection, no rate limiting, no account lockout:
Since there is no actual server-side session, there is no mechanism to
detect brute-force login attempts, no session expiry, and no way to
remotely invalidate a compromised session.
Impact
This affects every user who registers on EduBridge. With 70 active forks
and a live application, this is a critical vulnerability that needs
architectural resolution — not just a patch.
Proposed Fix
Backend — Flask session-based auth with hashed passwords:
# backend/app.py
from flask import Flask, request, jsonify, session
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3, os
app = Flask(__name__)
app.secret_key = os.getenv("SECRET_KEY") # must be set in .env
@app.route("/api/register", methods=["POST"])
def register():
data = request.get_json()
email = data.get("email")
password = generate_password_hash(data.get("password"))
# Store hashed password in SQLite/database
# ...
return jsonify({"status": "registered"})
@app.route("/api/login", methods=["POST"])
def login():
data = request.get_json()
user = get_user_by_email(data["email"])
if user and check_password_hash(user["password"], data["password"]):
session["user_id"] = user["id"]
return jsonify({"status": "ok"})
return jsonify({"error": "Invalid credentials"}), 401
@app.route("/api/me")
def me():
if "user_id" not in session:
return jsonify({"error": "Unauthorized"}), 401
return jsonify(get_user(session["user_id"]))
Frontend — replace localStorage auth with API calls:
// scripts/auth.js — replace localStorage pattern with:
async function login(email, password) {
const res = await fetch("http://127.0.0.1:5000/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include", // include session cookie
body: JSON.stringify({ email, password })
});
return res.json();
}
Acceptance Criteria
Labels: security, critical, backend, level: intermediate
Summary
login.html(138 lines, fully reviewed) submits the login form viascripts/auth.jswhich is a pure client-side authentication systemusing
localStorage. TheAuthclass inauth.jsstores usercredentials and session state entirely in the browser's
localStoragewith no server-side session validation, no JWT, and no password hashing.
This is confirmed by the
quiz.htmlauthentication check:And
login.htmlposts to no backend endpoint — the form'sid="loginForm"is handled entirely by
scripts/auth.js.Exact Security Failures
1. Plaintext password storage in localStorage:
localStorageis readable by any JavaScript executing on the same origin.A single reflected XSS vulnerability anywhere in the 8 HTML pages
(e.g., unsanitized quiz results rendering, career page content injection)
would expose every registered user's plaintext password to the attacker.
2. Authentication is bypassable with one browser console command:
3. The Flask backend at
backend/app.pyis completely bypassed:The
backend/directory exists and Flask is set up withpip install flask python-dotenv openai. But the actual login/register flow never makes anHTTP request to
http://127.0.0.1:5000/—auth.jshandles everythinglocally. The backend serves no authentication purpose.
4. No CSRF protection, no rate limiting, no account lockout:
Since there is no actual server-side session, there is no mechanism to
detect brute-force login attempts, no session expiry, and no way to
remotely invalidate a compromised session.
Impact
This affects every user who registers on EduBridge. With 70 active forks
and a live application, this is a critical vulnerability that needs
architectural resolution — not just a patch.
Proposed Fix
Backend — Flask session-based auth with hashed passwords:
Frontend — replace
localStorageauth with API calls:Acceptance Criteria
backend/app.pyimplements/api/register,/api/login,/api/logout,/api/mewith Werkzeug password hashingwerkzeug.security.generate_password_hash()output — never plaintext
scripts/auth.jsreplaced with fetch-based API calls.envdocumented withSECRET_KEYrequirementlocalStorageLabels:
security,critical,backend,level: intermediate