Treat Every ID as an Authorization Claim
Authentication answers one important question: who made this request? It does not answer a second question that often hides inside an ordinary-looking query parameter: may this caller name the record represented by this identifier? That gap is easy to miss in APIs that accept a parent ID, child ID, account ID, project ID, or tenant ID. The route is authenticated. The identifier has the correct…
When a system requires an identifier to fetch data, it's essential to treat that identifier as an authorization claim, not just a query input. Authentication proves who made the request, but it doesn't determine if the caller has permission to access the specific record represented by that identifier. Before using the identifier for querying, verify its scope against the caller's authorized access.
Consider an endpoint that returns activities for a household record. If a user changes the supplied ID, the query service might still return data for someone else's household. The mistake here is assuming that being signed in grants permission to access any record, rather than only the ones explicitly authorized for that user. The solution is to derive the caller's authority from trusted identity and relationship data before executing the query.
Email addresses are a poor choice for ownership verification because they can be outdated, shared, or copied between records. Instead, establish a direct connection between the caller's account and the specific records they should access. This approach prevents unauthorized access even if an identifier is slightly modified in transit.
When building a safe request pipeline, always check each identifier against the caller's authorized scope before constructing and running the data query. This early check prevents broad results from potentially exposing sensitive data later in the pipeline. Don't combine unrelated identifiers into a single filter; evaluate each one separately to avoid "one valid field makes the request valid" vulnerabilities.
For most data-specific routes, reject requests containing cross-scope identifiers outright. This approach is clear, observable, and easier to debug. Legacy systems might send old identifier formats, and rather than waiting for them to disappear, compatibility-preserving narrowing can provide a safe baseline response while still rejecting unauthorized data. However, this approach should be documented, used sparingly, and removed once the legacy system is fully phased out.
Testing the boundary between authorized and unauthorized requests is crucial. Create a matrix of test cases that cover various scenarios, such as when the caller names their own record, a related child record, another household's record, or an unauthorized record. Include tests for staff roles with and without cross-scope permissions, as well as for legacy request shapes. These tests should verify both the response status codes and the actual data returned, ensuring that no unauthorized information is exposed.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.