Authentication and authorization
Place authentication, object authorization and resource limits at explicit application boundaries.
THE IDEA
Authentication and authorization
Authentication answers "who is calling?" (no credentials: 401). Authorization answers "may this caller perform this action on this object?" (logged in but not the owner: 403). A private API needs both, checked on every object, because otherwise any logged-in user can just try other ids.
Authentication establishes an identity. Authorization checks the intended action on one particular resource. Both are needed, and a valid login must not open every object id.
Predict first. Does a valid login grant access to every object's ID?
Chapter context
A distributed application must identify callers and control which resources and effects they may access. Tenant boundaries and resource budgets must be enforced by trusted components rather than inferred from client-supplied identifiers.
TRY IT YOURSELF
Part 1. Do it hands-on
Pick the right status codes by hand, then write authentication, authorization and the handler.
Authentication establishes an identity. Authorization checks the intended action on one particular resource. Both are needed, and a valid login must not open every object id.
- To do: GET /docs/7 with no credentials at all. Status code (401 = not authenticated, 403 = authenticated but not allowed)?
- To do: Bob logs in successfully and requests /docs/7, which belongs to Alice. Status code?
- To do: Alice requests her own /docs/7. Status code?
- To do: Does a valid login grant access to every object's ID? Type yes or no
Part 2. Do it in code
- authenticate(token, sessions): sessions maps a token to a user name; return the user or None.
- authorize(user, action, doc, roles): doc is {"id", "owner"}; roles maps user → "admin" or "user". Allow "read" and "write" for the owner and for admins; deny everything else, and deny any action other than read/write/delete (admins may also delete; owners may delete too).
- handle(token, doc_id, action, sessions, docs, roles): returns the status: 401 if not authenticated; 404 if the document does not exist; 403 if authenticated but not allowed; otherwise 200.
- ids_visible(user, docs, roles): the sorted ids of documents the user may read.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Does a valid login grant access to every object's ID?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For a token bucket with capacity C, refill rate r, previous tokens b and elapsed time dt:
Accept cost k only when , then subtract k atomically. Clock behavior, concurrency and shared enforcement are additional implementation requirements.
Worked example
A bucket holds at most 10 tokens and refills 2/s. An 8-token request at time zero leaves 2. Two seconds later it has 6. A request costing 7 is denied and leaves 6; a request costing 6 is accepted and leaves zero. Rejected operations should not accidentally create negative credit under this contract.
Reference code
The following example uses the Python standard library and does not call a remote service.
capacity,rate,tokens=10,2,10
tokens-=8
assert tokens == 2
tokens=min(capacity,tokens+rate*2)
assert tokens == 6
assert tokens < 7
assert tokens >= 6
tokens-=6
assert tokens == 0
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
Authentication is not object-level authorization. Cache and worker paths can cross tenant boundaries too. A global rate limit requires a stated distributed enforcement contract.