Access, AI mistakes, and a checklist
how a backend differs from other code
What it is
The chapter's closer. Two topics: who is allowed to do what — and how to check backend code.
A backend differs from everything else in this book in one way: other people appear here. A mistake in a script costs you an evening; a mistake in access control on a server can cost other people's data.
Access
Two different questions that get confused constantly:
| Question | Term | Response code |
|---|---|---|
| Who are you? | authentication | 401 if you didn't identify yourself |
| What are you allowed? | authorisation | 403 if you did, but it isn't allowed |
The typical hole looks like this: the token is checked, the right is not.
@app.get("/subs/{user_id}")
def get_subs(user_id: int, current_user = Depends(get_current_user)):
return SUBS[user_id] # anyone can read anyone's subscriptionsThe user signed in honestly, the token is real — and they request
/subs/999. The identity check passed, the permission check does not
exist.
if current_user.id != user_id and not current_user.is_admin:
raise HTTPException(status_code=403, detail="not your data")What must not be handed out
- Tokens and keys — not into the code, not into a chat, not into logs (article 5.7).
- A dump of the live database — it holds other people's personal data. For debugging, make an anonymised extract.
- A traceback to the client — it shows the project's structure (article 7.2).
- Internal fields in a response — hashes, service identifiers (article 7.3).
AI mistakes in a backend
- Authentication without authorisation — covered above.
500instead of400— no validation, the server dies on malformed data.- The N+1 query — article 7.6.
- An f-string in SQL — injection, article 7.5.
- No transaction where several changes must happen together.
- No
response_model— everything travels out in the response. - A synchronous driver in asynchronous code —
sqlite3instead ofaiosqlite, which blocks the whole server (article 6.4). - No pagination.
GET /subsreturns every record. At a thousand it still works; at a million it takes down both the server and the client.
A checklist for backend code
Five steps on top of the general acceptance checklist (article 4.7).
1. Response codes
Check all four cases with real requests:
curl -o /dev/null -w "%{http_code}\n" .../subs/7 # 200
curl -o /dev/null -w "%{http_code}\n" .../subs/999 # 404
curl -X POST ... -d '{"city":"X"}' -w " %{http_code}\n" # 422
curl -X POST ... -d '{"city":"Oslo"}' -w " %{http_code}\n" # 409 on a repeat2. Other people's data
Sign in as one user, request another's data. It must be a 403.
3. Database queries
How many per HTTP request? More than two or three — look for N+1.
4. The irreversible
Does the diff contain UPDATE, DELETE, DROP, migrations? Each one
separately and carefully (articles 7.5, 7.7).
5. What travels out to the client
Look at the real response in full, not at the schema. Any extra fields?
What to tell the AI
Check this backend code against the list:
- is there a permission check on the specific record, not only
on the token
- which codes are returned on malformed data and on a missing record
- how many database queries per HTTP request
- are there f-strings in SQL
- what travels out to the client in the response
Which of these are problems? If none, say so.Can user 7 get user 8's data? Show me the request that does it,
or explain what prevents it.Cheat sheet
Authentication is who you are (
401), authorisation is what you may (403).The AI adds the first and forgets the second. Ask about permissions separately.
Eight common mistakes: permissions,
500, N+1, injection, transactions,response_model, synchronous driver, pagination.Checklist: codes · other people's data · query count · the irreversible · what reaches the client.
Check the codes with real
curl, not by reading the code.A dump of the live database does not go into a chat.