Authorization That Holds Up: Object Checks, Step-Up and Signed Requests
You'll learn to
- -Find and fix broken object-level authorization, where a logged-in user reads someone else's object
- -Model fine-grained permissions with roles, attributes and relationships, denying by default
- -Require step-up authentication for sensitive actions even with a valid session
- -Sign requests with a timestamp and nonce, and use mutual TLS to identify services
Most serious API breaches are not broken logins. They are valid logged-in users asking for someone else's data, and getting it. OWASP's API Security Top 10 lists this broken object-level authorization first, and it is easy to ship by accident because the code works perfectly for the honest case. This chapter covers the checks that close it, and the extra proof that sensitive actions and machine-to-machine calls deserve.
An Analogy: The Hotel Key Card
A key card opens your room and no other. Broken object-level authorization is a hotel where the card opens any door, as long as you change the number you type. Everyone is a verified guest, yet nobody checks whose room it is. The lock has to look at the door, not just at the card.
Broken Object-Level Authorization
An endpoint like GET /orders/1042 first confirms the caller is logged in, then loads order 1042. If it never asks whether the order belongs to the caller, any user can walk through the ids and read every order. Authentication answered 'who are you'. The missing question is 'may you touch this one'. Press Run to see the leak and the fix.
- -Check ownership (or the right relationship) on every read, update and delete of a specific object, including nested routes and bulk endpoints.
- -Answer 404 for objects the caller may not see, so the response does not confirm that the id exists.
- -List endpoints must filter by what the caller may see, not only by what they asked for.
- -Unguessable ids such as UUIDs are defense in depth, never the authorization itself.
The first two pictures compare the broken lookup with the fixed one. The third shows the step-up challenge described later in this chapter, where the API refuses a sensitive action until the user proves who they are again.
The handler loads the order by id alone, so Ana can read Ben's order.
The same request now ends in a 404, exactly like an id that does not exist.
A valid session is not enough. The API challenges, and the client sends the user to authenticate again.
Fine-Grained Models
Roles (admin, editor, viewer) are a start, but real products need more. Attribute-based rules add facts about the user, the object and the context: a manager may approve expenses in their own department under a limit. Relationship-based rules follow links: you can edit this document because you are in a team that was given edit access to its folder. Whatever the model, route every decision through one policy function, can(user, action, object), deny by default, and test it, so no endpoint invents its own rule.
Step-Up Authentication
A session that logged in an hour ago with a password is not enough to change the account's email, add a payee or send a large transfer. Step-up authentication asks for stronger or fresher proof at that moment. The API checks claims in the token such as when the user authenticated and how strongly. If they fall short, it answers 401 with a WWW-Authenticate challenge (RFC 9470 defines the error insufficient_user_authentication for this, and the challenge can carry the max_age and acr_values it wants), and the client sends the user through a new authentication that meets them, then retries.
Signed Requests
For webhooks and partner APIs, a bearer token proves little about the message itself. A signed request adds a keyed hash (an HMAC) over a timestamp, a unique nonce, the method, the path and a hash of the body, using a secret shared with the partner. The receiver recomputes it with a constant-time comparison, rejects timestamps older than a few minutes, and remembers nonces to refuse replays. Press Run to see four requests.
The first request is accepted. Sending the same nonce again is a replay. Changing the amount to 900 breaks the signature, because the body hash is part of what was signed. The last request carries a valid signature but is far outside the time window, so it is refused. A signature binds the whole message to the secret and to one moment.
Mutual TLS Between Services
With mutual TLS, both sides present certificates, so the server learns exactly which service is calling and the client knows it reached the real server. The service identity comes from the certificate, not from a header anyone can forge. In practice a mesh or sidecar issues short-lived certificates and rotates them automatically, and the receiving service then applies the same authorization rules to the identity it learned: authenticated is not the same as allowed.
Interview Signal is part of Pro
See a real weak answer next to a real strong one for this exact topic.
Quiz is part of Pro
Test what you just read with a short quiz, and bank the XP.
Object-Level Authorization, Service-to-Service Identity with mTLS, Signed Requests, Step-Up Authentication and Fine-Grained Authorization in the API Design Lab.
Discussion0
Join the Discussion
Sign in to leave comments, reply to others, or like insights.
No comments yet. Be the first to start the thread!