Partner API: IDOR, Injection, and UI-Only Authorization
A partner portal exposes two REST methods, GET /orders/{id} and POST /orders, consumed by 30 partners. All partners share one API key stored in a Site Property and sent in an X-Api-Key header. The POST method uses a SQL node that builds the insert by concatenating the request body's notes field into the statement; the developer says the portal is only used by trusted partners. The OrderDetail screen hides the Refund button from non-managers, but the RefundOrder server action is public and is invoked by a client action. Partner A reported seeing Partner B's order when changing the id in the URL. The team plans to fix this by making order IDs GUIDs instead of sequential integers.
1. Why is switching to GUIDs not a fix for the reported issue? What is the actual vulnerability and remediation?
Read the worked answer
The issue is broken object-level authorization, not guessable identifiers. The API accepts an order id and returns the order without checking that the authenticated partner owns it. GUIDs make enumeration harder, which is obscurity, not authorization; a leaked id from a log, an email, or a previous response still works, and the bug remains for any id an attacker obtains.
Remediation: derive the partner identity on the server from the API key, then scope every read and write to that partner. The lookup becomes GetOrderForPartner(orderId, partnerId), enforced in one place used by all endpoints. Return 404 for another partner's order rather than 403, so the API does not confirm existence. Add automated tests with two partner identities that assert one cannot read the other's order, and log access with partner id and order id.
The shared API key is part of the problem. One key for 30 partners means no attribution, no per-partner revocation, and a single leak compromises everyone. Issue per-partner keys, store them as secrets, support rotation, and rate-limit per key.
Keep the GUID change if you like it, but it is defence in depth at best; the authorization check is the fix.
2. Identify the injection risk and how to fix it. Why is trusted partners not a control?
Read the worked answer
The notes field is concatenated into SQL, so it is a SQL injection path. A partner can send a value that terminates the string and changes the statement, potentially reading or modifying other rows. The database runs the statement with the application's privileges, so the blast radius is everything that connection can reach.
Fix: use SQL node parameters (for example @notes) and bind the value, or use an entity Create action instead of raw SQL. Never enable Expand Inline on user-controlled values; reserve it for fixed identifiers you control. Validate length and allowed characters as defence in depth, and run the application with a least-privilege database user so a successful injection cannot escalate.
Trusted partners is not a control for three reasons: a partner's system can be compromised and then sends hostile input; a partner employee can misuse the integration; and trust is a property of the relationship, not of the bytes in the request. Every input crossing a trust boundary is untrusted, including fields that look boring like notes.
Also review logging: do not log the raw notes field or the API key, and return a generic error instead of database details.
3. How should refund authorization be enforced, and what else would you review in the API authentication design?
Read the worked answer
Hiding a button is user experience, not authorization. Any user who can call the client action, or call the server action directly, can trigger a refund. Enforce it in the server action: CheckRole for managers, verify the order belongs to the authenticated partner, verify the order state allows a refund, and verify the amount. Set roles on the action as a second layer. Return a generic failure and log the attempt with user, partner, and order.
Authentication review: the custom API key should be validated centrally before any method runs, rejected with 401 when missing or invalid, and never accepted from a query string. Use TLS only, rotate keys, and do not reuse the same key across environments. Check that the partner identity used for authorization comes from the key and not from a body field, that responses expose only needed fields, that rate limits and payload size limits exist, and that error messages do not reveal schema or other partners' data. Confirm there is no fallback anonymous access, and that the Site Property holding secrets is restricted and audited. Finally, test with two keys and a manager and non-manager user, not just with the happy path.