Learning paths / Vibe coding to production / Security of AI-written code

Authorization: who may do this?

Reading · 6 min · Module 6, lesson 2 of 417 min left in this module

Module 6 · Security of AI-written codeLesson 2 of 4

Goal: Find the starter's missing ownership check on delete, fix it, and ask "who may do this?" of every endpoint an agent writes.

Key idea

Authentication is knowing who's asking. Authorization is deciding whether they may do this, to this data. Agents build the feature and skip the second part, because the demo works without it. Every endpoint that reads or changes someone's data needs its own check.

Find it in the starter

app.delete("/api/tasks/:id", (req, res) => {
  if (!store.remove(req.params.id)) {
    return res.status(404).json({ error: "task not found" });
  }
  res.status(204).end();
});

The app knows who's asking: currentUser(req) reads the x-user header, and each task stores its owner. The delete route never compares them.

See it for yourself. Start the app and list the tasks: task 3 belongs to sam. Then, as demo (no header), delete it:

curl localhost:3000/api/tasks
curl -i -X DELETE localhost:3000/api/tasks/3

204 No Content: sam's task is gone, deleted by someone else.

Fix it

Look the task up, then check the owner before removing anything:

app.delete("/api/tasks/:id", (req, res) => {
  const task = store.all().find((t) => t.id === req.params.id);
  if (!task) {
    return res.status(404).json({ error: "task not found" });
  }
  if (task.owner !== currentUser(req)) {
    return res.status(403).json({ error: "not your task" });
  }
  store.remove(task.id);
  res.status(204).end();
});

Restart the app and run the delete again: 403 Forbidden. Add -H "x-user: sam" and it's 204. An ID that doesn't exist still gets 404. Run npm test to check nothing else broke, then commit the fix on its own.

Some apps answer 404 for other people's data too, so nobody can probe which IDs exist. Either is fine; pick one, write it in the brief, and test it.

Test the refusal, not just the success

The easy test deletes your own task and gets 204. It passed before the fix too, so it proves nothing about authorization. The test that matters sends a different user and expects 403, and it must fail against the old code (lesson 4.5.2).

Ask it of every endpoint

When you review an agent's diff, put these to each route:

  • Who may call it? Anyone, a signed-in user, an admin?
  • On whose data? Does it check the record belongs to the caller?
  • What if the ID belongs to someone else? Try it with curl.
  • What does it return to other users? GET /api/tasks lists everyone's tasks today. Is that what you want?

Put the rule in your brief's acceptance criteria, as the module 3 brief did: "Only the task's owner may change it; anyone else gets 403."

The x-user header is a toy

Any client can send any header, so x-user: sam proves nothing about who's really asking. The starter uses it to keep the lesson small. Real apps get the user from a sign-in: a session cookie or a token that the server verifies on every request. The authorization check stays exactly the same; only currentUser changes. Don't ship header-based identity, and don't let an agent add it to your app as a shortcut.

Check yourself

An agent's test for the delete fix sends x-user: demo and deletes demo's own task. Why isn't that enough?
Which question catches the starter's bug fastest in review?