Skip to content

Cybersecurity Engineering Handbook / Chapter 49

API Security Playbook

Secure REST, GraphQL, gRPC, event-driven APIs, and webhooks with object authorization, schema validation, abuse limits, and release review.

An invoice platform exposes refunds in five places. A partner calls a REST route. The support console uses a GraphQL mutation. A billing service invokes gRPC. A worker consumes RefundRequested. The payment provider reports completion by webhook. They look like separate interfaces, but they exercise one authority: move money for a particular invoice.

Suppose the REST route checks that the partner has a valid token, then accepts an invoice id from another tenant. The authentication is correct and the operation is still unsafe. Fix that route alone and the GraphQL resolver, worker, or replayed webhook may preserve the same defect. The useful unit of API security is the business operation across every path that can invoke or observe it: establish the caller, authorize the object and action from trusted state, constrain the message and its cost, return no more than the caller needs, and preserve evidence of the decision.

API security control map with REST, GraphQL, gRPC, events, and webhooks passing through gates for token authentication, schema validation, object authorization, rate limits, response minimization, and abuse logging.
API review should follow the operation through the same gates for every style, with broken object-level authorization tested at the object access point rather than only at the route.

Classify the operation, not only the endpoint

The refund deserves a stronger review than a public catalogue lookup because it changes money, crosses a tenant boundary, calls an external provider, and may be repeated asynchronously. Record those consequences before choosing controls. Also record every caller and path: customer, support agent, partner workload, billing service, queue consumer, scheduled reconciliation job, and provider webhook.

“Internal” describes network placement, not trust. A compromised workload, excessive service role, mistaken route, or forged event can reach an internal API. Each path still needs an authenticated identity, an explicit authorization decision, a bounded message, and telemetry. Classification determines how strong those controls must be: the refund may require recent human authentication for support, a narrowly scoped workload identity for services, per-tenant limits, and a release gate that a read-only endpoint would not.

Write the security claim before examining code:

This actor may request this amount of refund for this invoice in this tenant, once, through these paths; only these parties may learn the result.

Every phrase needs an enforcement point. If “this tenant” is checked only in the REST controller, the claim does not survive the GraphQL resolver or worker.

Establish identity without confusing it with permission

For a token-authenticated request, validation must establish that the credential was issued by a trusted authority, for this API and environment, within its valid time, using an allowed algorithm and key. Interpret subject, client, tenant, and scopes according to the issuer’s contract. Reject a token intended for a different audience or client type even when its signature is valid. High-impact operations also need deliberate revocation and key-rotation behavior; a validation cache must not silently extend authority beyond the system’s risk decision.

Service calls should carry a service or workload identity rather than a shared static secret. A service identity answers which workload called; it does not answer which invoice it may refund. Human support actions may additionally require recent authentication, phishing-resistant MFA, or just-in-time elevation according to risk. Preserve the human actor when a service acts on that person’s behalf so the downstream decision does not collapse into “billing-service did it.”

Events require the same clarity. A broker-authenticated producer can be permitted to publish RefundRequested without being entitled to choose any tenant or amount. The consumer should treat event fields as claims and re-establish the business authority it needs from trusted state. Transport trust does not turn payload data into policy.

Make object authority survive every representation

Broken object-level authorization occurs when changing an invoice id, tenant field, GraphQL node id, gRPC message field, cursor, event payload, or webhook reference reaches an object outside the caller’s authority. The defect often hides behind successful authentication and plausible input.

Resolve the invoice under authoritative tenant scope, then authorize the actor’s relationship, requested action, amount, invoice state, and relevant risk context before returning data or creating a side effect. For list and bulk operations, shape the query with authorization constraints; checking only the first row or filtering forbidden rows after a broad fetch can leak counts, timing, cached data, or individual records. Queued work must carry a trustworthy authorization context or re-evaluate policy when it runs.

The refund’s negative tests should substitute another tenant’s invoice through every representation. They should also attempt a support-only field through a partner route, a refund against an archived or already-refunded invoice, a mixed-tenant bulk request, a revoked grant followed by delayed queue execution, and a globally cached response. A route-level denial is weak evidence if another resolver or consumer still moves the money.

Field authority matters too. A caller permitted to refund an invoice may not be permitted to set approved, override the amount, select the destination account, or read internal fraud notes. Bind writable and readable fields to the actor and operation instead of deserializing a broad object model and hoping later code ignores the dangerous members.

Constrain the message and the work it can cause

Validate each representation against an explicit schema before expensive work. Reject malformed values and bound strings, numbers, arrays, nesting, batches, filters, deadlines, and decompressed message size. Decide how unknown fields behave. Silently accepting a new or obsolete field can preserve a mass-assignment path or create incompatible interpretations between old producers and new consumers.

Structural validity is only the first boundary. A refund amount may be a valid positive decimal yet exceed the remaining refundable balance. An event may have a valid invoice id yet arrive after the invoice changed state. Enforce business invariants at the authoritative state transition, where concurrency and prior actions are visible.

Responses need their own schema. Return the operation result and stable client-facing identifiers, not the persistence model. Excessive data exposure can occur when a generic serializer includes customer details, internal ids, permission hints, risk scores, provider payloads, hidden fields, or cross-tenant metadata that the caller never requested. Test the exact response shape for each caller, including errors and compatibility versions. A client that ignores an extra field has still received it.

Give GraphQL a cost and authority model

GraphQL lets the caller choose a response shape, so a top-level authorization check is insufficient. The refund mutation must authorize the invoice and action; each nested resolver must also enforce the authority needed for fields such as payment method, customer contact, risk decision, or audit history. Do not assume that reaching a parent object grants every child field.

Set limits that reflect execution cost: depth, field or resolver weights, aliases, fragments, list sizes, pagination, node count, and total execution time. A shallow query can still be expensive when it repeats costly aliases or fans out across large lists. Apply quotas to the resolved caller, tenant, operation, and calculated cost rather than relying on a single IP request count.

A GraphQL review record should name:

  • the mutation and every resolver capable of reaching refund or invoice data;
  • the object and field policy enforced at each boundary;
  • allowed input fields and the caller-specific response schema;
  • pagination, depth, alias, node, complexity, timeout, and batch limits;
  • production policy for introspection and error detail;
  • negative field-access tests and one worst-allowed-cost test.

Introspection is a deployment decision, not a substitute for authorization. Disabling it may reduce casual discovery; enabled or disabled, forbidden objects and fields must remain forbidden.

Treat delivery guarantees as part of authorization

REST retries, gRPC retries, broker redelivery, client timeouts, and webhook retries can all repeat an accepted action. A refund operation needs an idempotency key scoped to the caller and operation, stored with the authoritative result. The same key with the same request should return the prior outcome; the same key with different material parameters should fail. Make the retention window long enough for the delivery and reconciliation behavior the system actually supports.

Rate controls should follow both identity and cost. Bound refund attempts by actor, token or workload, tenant, organization, operation, amount or other risk unit, and source where useful. Separate per-request limits from business quotas such as total refunded value. Legitimate callers need predictable retry behavior, while denials should avoid revealing whether a foreign invoice exists.

Events also need event identity, duplicate handling, bounded retries, dead-letter ownership, and ordering assumptions. A monotonically increasing sequence may help where the producer can guarantee it, but consumers still need an explicit policy for gaps and out-of-order delivery. Never let “at least once” become “the business side effect may happen several times.”

Verify webhooks before granting them meaning

The payment provider’s webhook is an untrusted network request until verification succeeds. Preserve the raw request bytes needed by the provider’s signing scheme. Check the configured source or key identity, signature, covered content, timestamp or freshness data, and allowed replay window before parsing deep business content. Compare signatures safely, rotate secrets or keys deliberately, and reject unsupported signature versions rather than guessing.

Signature validity proves that the signer produced those bytes. It does not prove that the referenced refund belongs to the tenant, that the state transition is legal, or that the event is new. Match the provider account and external reference to server-side records, enforce the expected transition, and store the provider event id or another durable deduplication key before applying the side effect. Acknowledge and retry according to a defined failure policy; do not trade correctness for a quick success response.

A webhook verification record should identify:

  • the exact bytes and signature metadata covered by verification;
  • trusted keys or secrets, rotation behavior, and accepted algorithms or versions;
  • freshness and replay window, durable event-id deduplication, and retry semantics;
  • provider-account, tenant, object, amount, currency, and state-transition checks;
  • response behavior for invalid, duplicate, delayed, and temporarily unprocessable events;
  • redacted logs, alert conditions, and a reconciliation path for missed delivery.

Preserve evidence across the whole operation

The refund decision should retain a correlation or request id, actor and delegated human where applicable, service, tenant, operation, target object type and stable identifier, policy result, validation-failure family, idempotency outcome, rate or quota result, response class, client or schema version, and downstream event ids. Protect the log as sensitive evidence. Bearer tokens, signing secrets, full payment payloads, and unnecessary customer content do not belong in it.

Detection becomes useful when it follows the operation across paths: repeated cross-object denials, invoice-id enumeration, token audience failures, unexpected administrative callers, schema rejection bursts, costly GraphQL selection, webhook signature or replay failures, abnormal refund velocity, and disagreement between internal and provider state. Monitor collection and routing too; silence is meaningful only when the evidence path is healthy.

Review and test one operation before release

The release record for the refund should connect the REST route, GraphQL mutation, gRPC method, event producer and consumer, webhook, and compatibility versions to one business claim. Name each caller, identity mechanism, object policy, input and output schema, cost and replay control, log event, owner, rollback path, and time-limited exception. Old mobile clients, partner versions, deprecated fields, and legacy consumers remain part of the attack surface until they are removed or made unable to exercise the authority.

An API abuse test suite should make the claim fail one phrase at a time:

  1. Present expired, not-yet-valid, wrongly issued, wrong-audience, wrong-scope, and revoked credentials; verify both rejection and safe evidence.
  2. Substitute another tenant’s invoice and related identifiers through REST, GraphQL, gRPC, queued work, bulk operations, and caches.
  3. Attempt forbidden writable fields and request forbidden response fields; assert the complete response and error schemas contain no excess data.
  4. Send unknown, malformed, oversized, deeply nested, highly aliased, high-cost, and decompression-heavy messages; prove limits apply before disproportionate work.
  5. Repeat the same action with the same idempotency key, then reuse that key with changed amount, tenant, and invoice; exercise retries and concurrent delivery.
  6. Forge, alter, delay, and replay webhooks; rotate the signing key; deliver valid events out of order and more than once; reconcile a deliberately missed event.
  7. Exceed actor, tenant, operation, value, batch, and GraphQL cost limits; verify legitimate retry guidance does not disclose foreign-object existence.
  8. Trigger representative authorization, schema, complexity, replay, and velocity signals; trace redacted events through detection and an actionable response.

The operation is ready only when the same security claim survives every path. A valid token, green route test, or signed webhook is one piece of evidence. None can stand in for authority over the invoice, a bounded state change, or proof that the other representations did not preserve a way around the decision.