DEV Community

SorrelThorncroft
SorrelThorncroft

Posted on

Rich Authorization Requests Fail When Downstream Services Flatten the Details

Rich Authorization Requests Fail When Downstream Services Flatten the Details

OAuth scopes are good at broad categories. They are poor at describing a specific transaction, such as a payment to one beneficiary, a transfer from one account, or a document export limited to a date range. RFC 9396 adds authorization_details to carry structured authorization data for those cases. The specification is useful precisely because it preserves object-level detail. That detail can disappear when an application translates the grant into a simple list of scopes before calling downstream services.

Consider an application that receives authorization for an account_information object with a specific account identifier and a purpose. The authorization server returns a token with authorization_details. The application then calls its reporting service with a generic reports:read scope because that is what the service already understands. The reporting service cannot tell whether the user authorized one account or every account. A bug in the application's resource selector then becomes an authorization bypass, even though every token and scope check succeeded.

The failure is architectural. RFC 9396 expects the resource server to understand the specific authorization data relevant to its request. It also discusses transmission of authorization_details in the authorization request and the need for secure request handling. RFC 9126's pushed authorization request mechanism can help protect the integrity of a complex request before the user is redirected. Neither mechanism guarantees that a middle tier will preserve the object.

A strong implementation treats authorization_details as a typed contract. Define the supported type values, required fields, allowed locations, and the action each field authorizes. Reject unknown types by default. If a downstream service cannot process the detail, fail closed instead of replacing it with a broad scope. Keep the original authorization request and the issued token associated with the same transaction identifier. When an API receives a token, compare the requested resource with both the detail that the token represents and the route's scope.

Testing should include negative cases. Ask for one account but call another. Ask for a read purpose and submit a write. Include duplicate detail elements and unexpected fields. Try a nested object where the application expects a string. The goal is not to test JSON parsing in isolation. The goal is to prove that the authorization decision survives every translation layer between the browser and the data store.

Logging needs the same care. A log that records only scope=reports:read cannot explain why a particular report was allowed. Record the authorization type, the non-sensitive resource identifier, the grant identifier, and the policy decision. Redact account numbers or personal data as required. If a support team cannot reconstruct which object was approved, incident response will have to guess from application behavior.

Rich authorization requests are not a replacement for object-level access control inside the resource server. They carry the user's intent and constraints into that server. The server still has to check whether the identified object belongs to the user, whether the action is permitted, and whether the authorization is current. The value of the standard is that it gives the server something more precise than a broad scope to check against.

Teams adopting the standard should start with one high-risk workflow rather than converting every API at once. Pick a flow where a generic scope currently hides an important object choice. Preserve the structured detail end to end, add a deny path for unsupported details, and run the negative tests as part of the deployment gate. If a downstream service cannot express the decision, it should not receive the request through a widened substitute.

An authorization system loses its value when the only surviving statement is that someone approved "access". OAuth's structured authorization data is most useful when the exact object, action, and purpose remain visible at the enforcement point. Flattening those details may make integration easier, but it also turns a specific consent into a reusable permission. Preserve the detail, validate it where the data is read, and make the safe failure visible in logs and tests.

References

Top comments (0)