Where Authorization Is Enforced
The shop API’s implementation has three layers: the API layer, which reads each request and formats the response; the business logic layer, which implements the shop API’s rules; and the data access layer, which reads and writes the database. Before an operation runs, the server verifies who the caller is, checks that their role permits the operation, and, for a cart or order, checks that the caller owns it and that it is in the right state. In this section, we decide which layer runs each of those checks.
Authentication in the API layer
Authentication needs only the request. The credentials are in the Authorization header. So it runs before the resolver, in the API layer, as middleware: code that runs on every request before the code for that particular request. The authentication middleware reads the credentials, verifies them, and attaches the caller’s identity and roles to the request. If the credentials are missing or invalid, it refuses the request, and no resolver runs.
Authorization in the business logic layer
The role check, the ownership check, and the state check run inside the business operation. The role check comes first. The operation for createProduct starts by checking that the caller’s roles include merchant, and refuses with FORBIDDEN if they do not. The roles come from the authentication middleware, and what each role permits is defined once, in this layer.
The ownership check needs the cart. To decide whether shopper 7 may read cart 75, the server has to look up cart 75 and read its owner, and the cart is loaded inside the business operation. The state check needs the same cart.
The API layer receives a request, runs the authentication middleware, and passes the caller’s identity and roles to the business operation along with the requested cart identifier. The business operation checks the role, uses the data access layer to load the cart, and checks its owner and state. If the checks allow the request, the business operation returns the cart to the API layer. If they refuse it, the business operation returns the failure, and the API layer formats it as an error response.
sequenceDiagram
participant C as Client
participant A as API layer
participant B as Business logic layer
participant D as Data access layer
C->>A: Read cart 75, with shopper 7's credentials
A->>A: Verify caller is shopper 7, a shopper
A->>B: Read cart 75 for shopper 7
B->>B: Check that shopper 7 has the shopper role
B->>D: Find cart 75 owned by shopper 7
D-->>B: No matching cart
B-->>A: Not found
A-->>C: Response with NOT_FOUND error
Here, the data access code looks for a cart matching both the requested identifier and the caller’s identifier as owner. There is no match, so the business operation reports that the cart was not found.
For a write, the checks come before the change. If the server refuses a setCartItem request, the cart stays as it was. If the server refuses a placeOrder request, it creates no order and does not reduce any stock.
Every resolver that touches a cart calls the same business operation, so the checks in that operation cover all of them. If we had kept the REST version of the shop API, or if we change its interface later, the handler would call the same operation and get the same checks.
Web frameworks also offer a way to run the role check in the API layer, as a guard attached to a route, a decorator on a handler, or a directive on a GraphQL schema field that names the role the field requires. That puts the role names in the API code, and if we change the API, we have to write them again. For the shop API, the operations are few and the check is one line, so I keep it in the operation.
Multiple resources in one request
A GraphQL request can ask for several resources, and each one gets its own ownership check. For example, one query might ask for cart 73 and order 92. Shopper 7 owns cart 73, and order 92 belongs to shopper 8. The server returns cart 73 and a NOT_FOUND error for order 92.
What a gateway can check
An API gateway sees every request before it reaches the application, so it can run middleware for the whole application. Authentication and rate limiting fit there, because both need only the request. The role check and the ownership check need the shop API’s roles and carts, so they stay in the business operation. In our design, the gateway verifies credentials and applies rate limits, and the application runs the authorization checks.