Find and fix a broken access check

Reproduce an authorization flaw, fix it, and keep the regression tests.

Where do you want to run the labs?

Local machine instructions selected. You can switch at any time.

Switching changes the instructions only. Stop any exercise server or container yourself before starting the other route. Both routes use the same files and completion marks.

YOUR TASK · LOCAL MACHINE

Reproduce the access-control bug, add the missing ownership check, and get all five tests to pass.

Before you start

The extracted folder from lab 01, a text editor, and Node.js 24. No npm install is needed.

You’re finished when

Your edited app.mjs passes all five unchanged tests.

Go to step 1 ↓

Do the exercise on your computer. This page provides instructions and saves completion marks; it does not run commands or check your files.

Follow these steps

  1. Terminal · extracted starter folder

    Open the project terminal and check Node

    Use the same folder you extracted in lab 01. If you do not have it yet, complete the download step in lab 01.

    How do I open a terminal in the right folder?

    Windows: open the extracted folder in File Explorer. Click the address bar, type powershell, and press Enter.

    VS Code: open the extracted folder with File → Open Folder, then choose Terminal → New Terminal.

    macOS or Linux: open Terminal and type cd followed by the extracted folder’s path. If the path contains spaces, put it in quotes.

    The terminal must be in the folder containing app.mjs and security.test.mjs. Commands below are entered in that terminal, not in the browser address bar.

    TERMINAL
    node --version

    If Node is missing, install the version 24 release from the official Node.js download page, reopen the terminal, and retry.

    Check before continuing

    A version starting with v24. If you see “node is not recognized” or “command not found”, stop here and install Node first.

  2. Terminal · same folder

    Run the tests before editing anything

    TERMINAL
    node verify-lab.mjs baseline

    The starter deliberately lets Bob read Alice’s note. The two failing tests show the bug. Do not change the tests to make them pass.

    Check before continuing

    3 tests pass and 2 fail. This is the expected starting result, not a setup failure. If all 5 already pass, this copy may already be fixed.

  3. Text editor · app.mjs

    Insert the ownership check and save the file

    Open app.mjs. Find the comment // LAB 02: enforce record ownership here, before returning any record data. Inside readRecord(), insert the following block immediately below that comment and above the line that returns status 200.

    INSERT INTO APP.MJS
    if (record.owner !== user) {
      return { status: 403, body: { error: 'Forbidden' } };
    }

    Keep the existing missing-user check, missing-record check, and successful return. Save the file. Paste this code into the editor, not the terminal.

    Check before continuing

    The new check sits between the missing-record check and the successful 200 response.

  4. Terminal · same folder

    Run the same tests again

    TERMINAL
    node verify-lab.mjs fixed

    All five should now pass. If they do not, check that you saved app.mjs in this exact folder and placed the block before the successful return. Compare with the full solution below if needed.

    Check before continuing

    5 tests pass and 0 fail. Keep this fixed app.mjs for the remaining lessons.

Practice challenge: give Bob his own record

The guard should compare ownership for every record, not hard-code Alice. Try this extension after the main fix passes.

  1. Download ownership-challenge.test.mjs and save it beside app.mjs. Keep security.test.mjs unchanged.
  2. Run the command below. Expect 1 pass and 2 failures: record 2 does not exist yet.
  3. In app.mjs, add a second entry to the records Map: key '2', with id: '2', owner: 'bob', and title: 'Bob note'. Keep record 1 and the ownership guard.
  4. Save and rerun the challenge. Expect all 3 tests to pass, then verify the original 5 security tests still pass.
TERMINAL
node --test ownership-challenge.test.mjs

Success means Bob can read his own note while both users are denied access to the other person’s note. This challenge is optional; it does not change the main lab completion requirements.

Why it works & help if you get stuck

THE CHANGE, LINE BY LINE

One guard, in the right place.

Keep the authentication and missing-record checks. Add the ownership guard immediately after the record exists check, before the successful response.

APP.MJS / INSERT THE THREE + LINES
  const record = records.get(id);
  if (!record) return { status: 404, body: { error: 'Not found' } };
+ if (record.owner !== user) {
+   return { status: 403, body: { error: 'Forbidden' } };
+ }
  return { status: 200, body: record };

The + signs mark additions in this comparison. Do not paste the + signs into JavaScript.

  1. Use the server’s record owner. The comparison uses ownership in the service’s data, not an owner value supplied by the caller.
  2. Check after finding the record. There is no owner to compare if the record does not exist.
  3. Return only the error. A 403 status with the record still in the body would leak the same data.
Compare with the full fixed app ↓

To use that file, save it as app.mjs in a separate copy of the kit. It uses the same synthetic demo identities.

RequestExpected statusWhy
No or unrecognized identity401The caller has not authenticated.
Alice asks for record 1200Alice owns the record.
Bob asks for record 1403Bob is authenticated but does not own it.
Alice asks for record 999404The record does not exist.
EXCERPT / VERIFIED NODE RUN
✔ anonymous callers receive 401
✔ the owner can read their record
✔ a different user cannot read the record
✔ a missing record returns 404
✔ HTTP authorization boundary and health route
ℹ tests 5
ℹ pass 5
ℹ fail 0

Timings are omitted. The HTTP test also checks the owner response, unknown tokens, missing records, and unsupported methods.

IF YOUR RESULT IS DIFFERENT

Common wrong turns

What happenedWhy it mattersWhat to do instead
Add only “if (user)”.Bob has a valid identity too. The original bug remains.Compare user with record.owner.
Return 403 for everyone.The owner test fails. This wrong fix was checked during course validation.Keep Alice’s successful case.
Change the status but return the record.The title is still exposed.Return an error object without record data.
The file no longer parses.Diff + markers or curly quotes may have been pasted.Use plain JavaScript and rerun the unchanged tests.

TRY WITHOUT COPYING · OPTIONAL

Explain the condition

Without copying the fix, explain why the ownership check must happen before returning the record. What would happen if you checked afterward?

Compare your reasoning

Once the server has returned private data, a later check cannot take it back. Reject the request before sending that data.

Explain your answer aloud or write a sentence before opening the explanation. This does not change your completion record.

A QUICK CHECK

Which check fixes the reported flaw?

Choose an answer
Compare with the solution +
if (record.owner !== user) {
  return { status: 403, body: { error: 'Forbidden' } };
}
Node.js test runner ↗

Done with this one?

Marking complete is your own record.

Confirm your results

Confirm both results before marking this lab complete.