Security Boundaries & Failure Modes
Security boundaries and failure modes are the points where a system must decide whether an actor is allowed to perform an action, and the ways that decision can be bypassed or made incorrectly. In user systems, the boundary must be enforced by trusted server-side code on every route to the protected effect.
This becomes necessary because users do not have to use your product through the interface you designed. They can call APIs directly, change identifiers, replay requests, skip screens, or trigger paths the UI never exposes. The button being hidden is only a presentation choice. The real question is whether a forbidden request, arriving at any server entry point, is refused before it changes or reveals something protected.
The practical method is to trace each capability from input to effect. For every operation, identify the actor, the resource, the action, and the server code that answers: may this actor do this to this resource now? That check should sit close to the protected action, not just near the visible page. Authentication establishes who is calling; authorisation decides what that caller may do in this context.
The trade-off is repetition and design pressure. If every handler, resolver, worker, webhook, admin task, and object lookup needs the right decision, scattered checks become hard to reason about. Centralising policy helps, but can hide missing object-level checks if used carelessly. The honest answer is usually: it depends on where the protected effect happens, and whether all alternate paths reach the same enforcement point.
Engineers meet these boundaries in HTTP routes, service methods, GraphQL resolvers, background jobs, message consumers, file downloads, cache reads, database queries, admin tools, and direct object fetches. The common failure mode is checking only the main screen or controller while another path reaches the same data or mutation. Good tests include crafted requests from unauthorised actors, not only happy paths through the UI.
Common questions
- Is the frontend ever a security boundary?
- No. The frontend can guide users by hiding controls, disabling fields, or filtering visible data, but it runs in an environment the user controls. Treat it as convenience and feedback, not enforcement. The server must still reject a crafted request that asks for the hidden action or unauthorised object.
- How is authorisation different from authentication?
- Authentication answers who the caller is, or whether they are anonymous. Authorisation answers whether that caller may perform a specific action on a specific resource in its current state. A valid login does not automatically grant ownership, tenant access, role permission, administrative power, or permission to perform a state transition.
- Where should the authorisation check live?
- Place it as close as practical to the protected effect, such as the object load, service method, mutation, query, or file read that exposes or changes data. Checking only at the page or route level is fragile because another route, worker, resolver, or administrative path may reach the same effect without passing through that code.
- What is the fastest way to find missing boundaries?
- List the capabilities in the feature, then enumerate every path that can reach each one: routes, jobs, webhooks, resolvers, admin flows, direct fetches, and queries. For each path, name the actor, resource, action, and server-side policy decision. Then add tests proving unauthorised crafted requests are rejected.