Student worksheet: Can you read the password?
Your mission
The campus club app accepts the correct password. Does that mean it stores passwords safely? You will inspect the supplied app, make a two-line repair, and prove that both login behavior and storage are correct.
Work only with this local lab and fictional passwords. Read safety. Allow 75–90 minutes after setup.
1. Prepare your workspace (5 minutes)
Your app folder means exercises/07-cryptographic-failures/student in the repository, or app in an exported student kit. All commands below run from that folder.
Open the app folder in your editor and its terminal. Type node --version and press Enter. You need Node 22+. Then run:
node server.mjs
Leave it running and open http://127.0.0.1:3000 in a browser. If you see a different lesson, stop its server with Ctrl+C first.
Open the submission template and use Save As to make your own copy. Each new result can replace the previous display, so copy evidence as you go. Plain copied output or screenshots are both acceptable; your instructor sets the final submission format and deadline.
2. Discover the problem (15 minutes)
- Leave the registration password as
Campus-demo-42!. Click Create / replace account. A 201 response means the account was created. - Click Check password with
Campus-demo-42!in the second field. Expect 200 and “Password accepted.” Save this response immediately under Before: login behavior; the next attempt replaces it. - Change only the second field to
Wrong-demo-42!and check again. Expect 401 and “Password not accepted.” Save that response too. These numbers are HTTP status codes, labels describing the server's response. - Click Inspect stored value. Copy the entire record to your submission under Before: storage A. JSON is a text format containing named values;
storedValueis the password representation, not a separate password. - Click Try Base64 decoding. Copy the result under Before: decoding. Does
recoveredPasswordreveal the fictional password? - Keep the registration field unchanged and click Create / replace account again. This replaces the same account, not a second user. Inspect again and save Before: storage B. Compare the two stored values.
- Confirm you saved both accepted and rejected results under Before: login behavior. Registration clears previous results, so rerun the password checks if you missed either.
The inspector deliberately shows server storage to help you learn. A real login app would not offer this view. That visibility is not the flaw you are repairing; storing passwords reversibly is.
3. Learn the small amount of theory you need (10 minutes)
| Term | What it means in this lesson |
|---|---|
| Encoding | Changes how data is represented. Base64 can be decoded without a secret key. It does not protect a password. |
| Encryption | Uses a key to recover the original data. This is useful for some secrets, but it is not the approach we use for password verification. |
| Password hashing | Produces a value used to check a password without providing a decoding step. Use a purpose-built password-hashing function rather than designing your own. |
| Salt | A fresh random value used when creating a password hash. It is stored with the hash and is not secret. It makes the same password produce different records and reduces reuse of precomputed guesses. |
| Verification | Uses the salt and settings already in the stored record to test a submitted password. It does not decode the original password. |
Hashing does not prevent someone from guessing passwords and checking those guesses. The supplied scrypt helper makes each guess deliberately expensive. A plain fast hash such as SHA-256 by itself is not a substitute for a password-hashing function.
4. Repair two lines (15 minutes)
Open password-store.mjs in the app folder. Do not edit the helper, HTML, server, or checks.
importmakes the supplied helper functions available.- A
functionis a named operation. The value in parentheses is its input. returngives a result back to the caller.asyncandawaitallow the app to wait for a hashing operation to finish. The functions are already markedasync.
The file currently contains:
export async function storePassword(password) {
return encodePassword(password);
}
export async function checkPassword(password, stored) {
return matchesEncodedPassword(password, stored);
}
Choose the two appropriate helpers from this table and change only the two return lines. Keep the imports, function names and braces.
| Helper | Result | Call form |
|---|---|---|
| encodePassword | Reversible Base64 text | encodePassword(password) |
| matchesEncodedPassword | Checks a password against Base64 storage | matchesEncodedPassword(password, stored) |
| hashPassword | New password-hash record with a fresh salt | await hashPassword(password) |
| verifyPassword | Checks a password using an existing hash record | await verifyPassword(password, stored) |
Write return, then the selected call form, then a semicolon. Use the helper that creates a hash for storage and the one that verifies an existing record for checking.
Why not hash the submitted password again and compare the two full strings? A new hash uses a new random salt. Verification must reuse the salt recorded during registration.
5. Restart and prove the repair (15 minutes)
Save the file. In the running terminal press Ctrl+C, then run node server.mjs again. Refresh the browser. Create the account again with Campus-demo-42!: the old account was cleared on restart.
Repeat the steps from section 2 and save the same before/after evidence:
- Correct password accepted; wrong password rejected.
- After: storage A, then the decoding result.
- Re-register the same password and save After: storage B.
- Check the correct password again after re-registration.
The stored record should start with scrypt$. Its fields contain the algorithm, settings, salt, and hash, not the plaintext password. recoveredPassword: null means the lab did not recover a password by decoding, not that all password attacks are impossible. The two records should differ even though the password is the same.
6. Run the checks (5 minutes)
Stop the server with Ctrl+C. In the same app folder, run:
node checks.mjs
The checks start their own temporary local server and stop it automatically. They do not use the account you created in the browser. The untouched starter passes five checks and fails the storage and salt checks. After your repair, expect All 7 checks passed. Copy the complete output.
Checks cannot replace your explanation. Passing these seven tests is not a production-security certification.
7. Explain and submit (10–20 minutes)
Complete the submission template and provide your edited password-store.mjs:
- Why did the original app accept the right password yet still have unsafe storage?
- Why is Base64 encoding not encryption or password hashing?
- Why do two registrations of the same password now differ, and how can login still work?
- What does the decoding result establish, and what does it not establish?
- Name one additional protection a real login system would need.
If something goes wrong
| Symptom | What to do |
|---|---|
| Node not found or older than 22 | Ask the instructor for the approved installation method. |
| Cannot find server.mjs | Open the terminal in the app folder, not the repository root or ZIP. |
| Port 3000 in use or another lesson appears | Stop the other lesson's terminal with Ctrl+C. |
| Could not reach the local app | Start node server.mjs, refresh, and create the account again. |
| No account exists after restart | Expected: accounts are temporary. Register again before inspecting or checking. |
| Code change has no effect | Save, stop the server, restart it, refresh, and re-register. |
| All logins fail after repair | Change both return lines. Use verification, not fresh hashing or string equality, for password checking. |
| SyntaxError in the terminal | Compare the function structure above. Check spelling, parentheses, braces and semicolons; save and restart. |
| Hashing takes a moment | Wait for the buttons to re-enable. Close duplicate labs. Ask for help if the operation fails; do not lower the helper settings. |
| Stored hashes differ from the screenshots | Expected: salts are random. Compare structure and behavior, not exact text. |