API Security, Authentication and Authorization Questions
Controlling who can call an API, what they may do, and defending it against abuse. Covers the access-control mechanics: API keys, OAuth 2.0 flows, OpenID Connect, JWT issuance/validation, session vs. token auth, scopes/roles for fine-grained authorization, token lifetime and refresh, mutual TLS, and machine-to-machine vs. user-delegated access. Also covers the adversarial hardening view: input validation, injection and deserialization risks, broken object-level authorization (BOLA), mass assignment, secrets handling, and the OWASP API Security Top 10, plus securing data in transit, preventing enumeration/scraping, and testing APIs for vulnerabilities.
Walk me through mutual TLS (mTLS): how does the handshake differ from standard one-way TLS, how do the client and server present and verify each other's certificates, and what are the practical options for certificate provisioning and rotation for service-to-service authentication in a microservice or service-mesh deployment?
Sample Answer
Direct answer
Mutual TLS (mTLS, where "TLS" is Transport Layer Security, the protocol that encrypts and
authenticates network connections) extends standard one-way TLS by having the client also
present a certificate that the server verifies, so both sides cryptographically prove their
identity to each other before any application data flows. In standard one-way TLS only the
server proves who it is; the client stays anonymous at the transport layer and any identity
checking happens later, in the application (a password, a bearer token).
Structured elaboration
How the handshake differs from one-way TLS. In a plain TLS handshake, the server sends its
certificate and the client verifies it against a trusted certificate authority (CA); the client
never presents one of its own. In an mTLS handshake, the server additionally sends a
CertificateRequest message, the client responds with its own certificate plus a
CertificateVerify message (a signature over the handshake transcript, proving the client
actually holds the private key matching that certificate, not just a copy of the public
certificate), and the server verifies the client's certificate against its own trusted CA
(commonly a private, internal CA for service-to-service traffic rather than a public one) before
completing the handshake. If either side's certificate fails verification, the connection is
refused before any request or response is exchanged.
What each side verifies. The client checks the server's certificate chain up to a trusted
root, the certificate's validity window, and that the certificate's subject matches the hostname
being connected to (the checks that already happen in ordinary HTTPS). The server does the
mirror image for the client: chain validity, expiry, and (this is the part unique to mTLS as an
authentication mechanism) that the certificate's identity (its Subject or a SPIFFE-style URI in
the Subject Alternative Name) matches an identity the server is willing to trust, and that the
certificate has not been revoked.
Provisioning and rotation options for service-to-service auth:
- Long-lived certificates issued manually or by a slow internal PKI (public key
infrastructure, the systems and processes for issuing and managing certificates), rotated
every 6 to 12 months. Simple to reason about but a compromised private key stays valid for a
long time, and rotation is often a manual, error-prone, outage-risking event precisely because
it happens rarely. - Short-lived certificates (hours, not months) issued automatically by an internal CA and
rotated continuously, often via a sidecar proxy that handles issuance and rotation
transparently to the application. This is the pattern service meshes like Istio or Linkerd
use, frequently paired with SPIFFE (Secure Production Identity Framework For Everyone, a
standard for representing workload identity as a URI) so identity is tied to what a workload
is (its service account, its namespace) rather than to a long-lived secret. Short TTLs shrink
the blast radius of a leaked key dramatically, since the certificate expires on its own within
hours even if revocation never fires. - A managed cloud CA or secrets-manager-backed issuance for teams that do not want to
operate their own internal CA, trading some control for less operational burden.
Worked example
sequenceDiagram
participant C as Client
participant S as Server
C->>S: ClientHello
S->>C: ServerHello plus Server Certificate
S->>C: CertificateRequest
C->>S: Client Certificate
C->>S: CertificateVerify (signed with client private key)
S->>S: Verify client cert against trusted CA, check chain and revocation
C->>S: Finished
S->>C: Finished
Note over C,S: Both sides now hold a mutually authenticated encrypted channel
The two extra round-trip messages compared to one-way TLS are CertificateRequest (the server
asking) and the client's Client Certificate plus CertificateVerify pair (the client proving
it holds the matching private key, not merely presenting a public certificate it copied from
somewhere). Everything after Finished on both sides is identical to ordinary TLS: an encrypted
channel, just one where the server now also knows, cryptographically, which specific
service-identity it is talking to, without needing an application-layer credential to establish
that.
Trade-offs and pitfalls
- mTLS proves connection identity, not user identity. It tells a server which service or
workload is calling, which is exactly the right primitive for service-to-service traffic in
a microservice or service-mesh deployment; it is the wrong tool for authenticating an
individual end user, since a human does not carry a private key the way a workload's sidecar
does. OAuth 2.0 or session-based authentication remains the right layer for user identity, and
the two are often combined (mTLS between services, a user token passed through inside that
channel). - Short-lived automated certificates need working automation, not just working crypto. If
the sidecar or agent responsible for rotation fails silently, certificates expire and every
connection using them starts failing at once, an outage that a slower manual rotation schedule
is less exposed to (at the cost of a much larger blast radius if a key leaks). Automated
rotation needs its own monitoring and alerting on issuance failures, not just on the
certificates' expiry dates. - Common wrong turn: skipping revocation checking because "the certificates are short-lived
anyway." Short TTLs reduce how long a compromise matters, but do not eliminate the window
entirely; a certificate stolen minutes before use is still valid until it expires, so
revocation checking (or, more commonly in practice, simply re-issuing all workload identities
and rotating the internal CA's trust) still matters for a confirmed compromise. - Common wrong turn: terminating mTLS at a load balancer and trusting an internal header for
identity afterward. If the load balancer is not itself inside the trust boundary you are
defending, or if an internal service can be reached directly without going through it, an
attacker who reaches the internal network can forge the identity header the same way they
would forge any other unauthenticated claim.
Describe a secure key management and rotation architecture for API signing and encryption keys. Cover the roles of HSM/KMS vs Vault, envelope encryption, automated rotation schedules, key versioning, secure provisioning to services, access control policies, and rollback strategies if a key is compromised.
Sample Answer
Direct answer
A secure key management architecture separates who is allowed to use a key from who can see
the key's raw bytes: application services request cryptographic operations (sign, encrypt,
decrypt) through an API backed by a hardware security module (HSM, a dedicated physical or
cloud-managed device for storing keys and performing crypto operations without ever exposing the
raw key) or a cloud key management service (KMS), rather than ever pulling the raw key material
into application memory. Automated rotation on a defined schedule, envelope encryption for bulk
data, and a rehearsed rollback plan for a suspected compromise complete the picture.
Structured elaboration
HSM/KMS vs. Vault. An HSM (or a cloud KMS built on HSM-backed key storage, like AWS KMS or
Google Cloud KMS) is purpose-built for one thing: keys that never leave it in plaintext, with
every cryptographic operation performed inside the device or service and only the result
(a signature, a ciphertext) returned to the caller. HashiCorp Vault (a secrets-management
platform) is broader: it manages many kinds of secrets (database credentials, API tokens,
certificates, and also encryption keys via its own key-management backends), often layering on
top of or delegating hardware-backed storage to a KMS/HSM underneath for the highest-sensitivity
keys, while adding features like dynamic, short-lived credential issuance for things that are not
purely "an encryption key," like database passwords. In practice, a mature setup often uses both:
KMS/HSM for the small number of long-lived signing and master keys, Vault for the broader
population of API-facing secrets and dynamically issued credentials.
Envelope encryption. Rather than encrypting every piece of data directly with a single
master key (which would mean any bulk decryption needs an HSM call and makes rotating the master
key mean re-encrypting everything it ever touched), envelope encryption generates a unique data
encryption key (DEK) per data item or batch, encrypts the actual data with that DEK locally
(fast, no HSM round trip needed), and then encrypts (wraps) only the small DEK itself using the
HSM-held master key (KEK, key-encryption key). Rotating the master key then only means
re-wrapping the small DEKs, not re-encrypting the underlying bulk data.
Automated rotation schedules. Signing and encryption keys should rotate on a defined cadence
(commonly 90 days for many compliance frameworks, though the right interval depends on the key's
exposure and the standard you are held to) via automation, not a manual calendar reminder, since
manual rotation is exactly the kind of rarely-exercised process that breaks silently when it is
finally needed. Automation should generate the new key version, begin using it for new
operations, and keep prior versions available for verification/decryption of data signed or
encrypted before the rotation, until that older data ages out.
Key versioning. Keys are never destructively replaced in place; each rotation creates a new
version while prior versions remain available (for verifying old signatures or decrypting old
ciphertext) until nothing depends on them anymore. Every signature or ciphertext should carry, or
be resolvable to, which key version produced it, so verification always uses the matching
version rather than assuming "the current key" is always correct.
Secure provisioning to services. Services should authenticate to the KMS/HSM/Vault using
their own workload identity (a short-lived certificate or token tied to what the service is,
renewed automatically, the same pattern well-run service meshes use for service-to-service
authentication), not a static credential baked into configuration, and should request only the
specific operations and key scopes they need (a service that only ever verifies signatures should
not hold a grant to sign).
Access control policies. Scope every grant to the narrowest key, operation, and identity
combination that satisfies the actual need: "this specific service identity may call decrypt
using this specific key" rather than a broad grant across all keys or all operations, and log
every key-use request (which identity, which key, which operation, when) so unusual access
patterns are visible after the fact.
Rollback strategy if a key is compromised. Have a rehearsed, not just documented, procedure:
revoke or disable the compromised key version immediately (stopping new operations with it),
rotate to a fresh key version, re-encrypt or re-sign anything that specifically depended on the
compromised version's continued trust (this is where envelope encryption pays off, since only
the wrapped DEKs need re-wrapping with the new KEK, not the bulk data itself), and audit the
access log for the compromised key to scope what, if anything, was actually exposed during the
compromise window.
Worked example
A payments service signs outbound webhook payloads with a signing key held in a KMS. Rotation
cadence: 90 days, automated. On day 90, the automation generates key version v7, begins signing
new webhooks with v7, and keeps v6 available for verification only (a partner's system might
still be validating a webhook signed just before the rotation, using cached knowledge of v6).
After a defined overlap window (long enough that no in-flight signed payload from v6 is still
being verified anywhere), v6 is retired from active use but its public verification material
stays available for as long as audit or dispute resolution might need to verify historical
signatures. If v7's private key material were suspected compromised on day 95, the rollback
plan disables v7 immediately, generates v8 for new signing, and the access log for v7 gets
audited to determine the exact window and scope of exposure, since "we rotated the key" alone
does not answer "what did the attacker actually get to sign or decrypt before we noticed."
Trade-offs and pitfalls
- Frequent rotation adds real operational overhead (key-version bookkeeping, overlap-window
management) in exchange for a smaller blast radius per key. The right interval balances that
against how sensitive the key is and what compliance framework, if any, sets a floor; rotating
a low-sensitivity internal key on the same aggressive schedule as a payments-signing key adds
cost without a matching security benefit. - Envelope encryption adds a layer of indirection that is easy to get subtly wrong, most
commonly by caching a decrypted DEK longer than intended (defeating the point of only
decrypting it when actually needed) or by failing to re-wrap old DEKs during a KEK rotation,
silently leaving old data protected only by a retired key. - Common wrong turn: treating "we have a KMS" as equivalent to "keys are managed securely."
A KMS with overly broad access grants (every service can calldecrypton every key) gives
you hardware-backed storage without the access-control discipline that actually limits blast
radius; the KMS is necessary but not sufficient. - Common wrong turn: documenting a rollback plan but never rehearsing it. A compromise
response that has never been exercised tends to reveal missing pieces exactly when there is no
time to discover them, for example, a service that hard-codes a specific key version instead of
always resolving "the current version" and so does not pick up the rotation automatically.
Architect a security model for a large-scale microservices platform (~1000 services) that uses a service mesh (e.g., Envoy/Istio) and an API gateway. Goals: enforce strong service-to-service authentication and authorization, minimize blast radius, centralize policy where sensible but avoid bottlenecks, ensure observability and incident response. Provide key components, identity model, policy enforcement points, rollout plan and scaling considerations.
Sample Answer
Direct answer
At roughly 1,000 services, service-to-service authentication and authorization has to be
enforced by infrastructure (a service mesh sidecar, such as Envoy, paired with a control plane
like Istio) rather than by each service's own application code, because you cannot reliably
audit or update security logic duplicated across a thousand codebases owned by many different
teams. The mesh's sidecar proxies handle mutual TLS (mTLS) and per-request authorization
uniformly, the control plane distributes identity and policy to every sidecar, and the API
gateway stays a separate, thinner layer that only handles edge concerns (external traffic
authentication, coarse rate limiting) rather than trying to be the single point enforcing every
internal rule.
Structured elaboration
Identity model. Every service instance gets a workload identity, most commonly a SPIFFE
(Secure Production Identity Framework For Everyone) identity encoded into a short-lived X.509
certificate, tied to what the workload is (its Kubernetes service account and namespace, for
example) rather than a static shared secret. The mesh's control plane (Istio's istiod, for
example) issues and continuously rotates these certificates automatically; no individual service
team manages its own certificate lifecycle.
Enforcing service-to-service auth and authorization. The sidecar proxy next to each service
terminates and originates mTLS transparently, so two services communicate over an encrypted,
mutually-authenticated channel without either one's application code implementing TLS itself.
Authorization (which services may call which other services, and for which operations) is
expressed as policy (Istio's AuthorizationPolicy resources, for example) and enforced by the
sidecar before a request ever reaches the application, giving every service the same
authorization enforcement mechanism regardless of what language or framework it is written in.
Policy enforcement points. At this scale there are exactly two places policy actually gets
enforced, and each has a distinct job: the API gateway is the enforcement point for traffic
entering the mesh from outside (external clients, partners), handling coarse checks like
authentication and rate limiting once at the edge; each service's own sidecar is the enforcement
point for everything after that, deciding, per call, whether one internal service may reach
another. Keeping these two enforcement points separate, rather than routing all internal traffic
back through the gateway, is what keeps the gateway from becoming a bottleneck for traffic that
never needed to leave the mesh.
Minimizing blast radius. Default-deny between services (a service can only call, or be
called by, the specific services its policy explicitly allows) turns a compromised service into
a contained incident instead of a pivot point to the rest of the fleet; without this, one
compromised service with network reachability to everything else effectively compromises the
whole mesh.
Centralizing policy without creating a bottleneck. Policy is authored and versioned
centrally (so the security posture is auditable in one place, as code) but distributed to
every sidecar so each one can make allow/deny decisions locally, at line rate, without a
synchronous call back to a central decision service on every request. This is the key design
move for enforcing at 1,000-service scale: centralize the authoring and distribution of
policy, not the evaluation of it.
Observability and incident response. Every sidecar can emit consistent access logs, metrics,
and distributed traces for the traffic passing through it, giving uniform visibility across all
1,000 services without depending on each service team to instrument request-level auth logging
themselves. This uniformity is what makes incident response at this scale tractable: a security
team traces a suspicious request across service boundaries using the mesh's own telemetry rather
than reconciling a thousand different logging formats.
Rollout plan. Introduce the mesh incrementally rather than flipping mTLS enforcement on
everywhere at once: start in permissive mode (the sidecar accepts both plaintext and mTLS
traffic while metrics show which callers have and have not migrated), onboard services namespace
by namespace or team by team, and only flip to strict mTLS enforcement for a given service once
its traffic is confirmed fully migrated. A big-bang cutover at 1,000 services risks a
simultaneous outage across the fleet if any meaningful fraction of callers have not yet adopted
the sidecar.
Scaling considerations. The sidecar's own resource footprint (CPU and memory per pod) and the
control plane's config-push fan-out both need capacity planning at this scale; a control-plane
change that pushes new policy to 1,000 sidecars simultaneously needs to be paced (canaried,
rate-limited) rather than broadcast all at once, since a bad policy pushed everywhere
simultaneously turns a policy bug into a fleet-wide outage instead of a contained one.
Worked example
graph TD
Ext[External Traffic] --> GW[API Gateway - Edge AuthN and AuthZ]
GW --> SM[Service Mesh Ingress]
SM --> SVA[Service A plus Envoy Sidecar]
SM --> SVB[Service B plus Envoy Sidecar]
SVA -->|mTLS plus SPIFFE ID| SVB
CP[Istio Control Plane] -.->|distributes policy and certs| SVA
CP -.-> SVB
SVA --> OBS[Telemetry: access logs, traces]
SVB --> OBS
The gateway handles only the edge boundary (authenticating and rate-limiting external traffic
before it enters the mesh at all); everything after that, service A calling service B, for
example, goes sidecar-to-sidecar over mTLS with an authorization decision made locally by
service B's own sidecar, based on policy the control plane already pushed to it. If the control
plane is briefly unavailable, existing sidecars keep enforcing their last-known policy and
certificates until they expire, since evaluation never depended on a live call to the control
plane; only new certificate issuance and policy updates pause during that window, which is
the direct payoff of "centralize distribution, not evaluation."
Trade-offs and pitfalls
- A service mesh adds real operational complexity and per-request latency overhead (an extra
network hop through the sidecar in each direction); this is a deliberate trade against the
alternative of every service reimplementing TLS and authorization independently, which does not
scale to 1,000 services being maintained correctly and consistently over time. The trade-off
is worth stating explicitly rather than presenting the mesh as free. - Permissive mode during rollout is a temporary state, not a target state. Leaving services
in permissive mode indefinitely (common when a migration stalls) means mTLS is not actually
being enforced for those services even though the infrastructure exists, which is easy to miss
in metrics that only measure "sidecar deployed" rather than "strict mode enforced." - Common wrong turn: routing all internal traffic back through the central API gateway to
reuse its authorization logic. This defeats the scaling argument for a mesh in the first place
and turns the gateway into both a latency bottleneck and a single point of failure for traffic
that never needed to leave the mesh's own service-to-service path. - Common wrong turn: pushing a policy change to all 1,000 sidecars simultaneously without a
canary. A syntactically valid but logically wrong authorization policy (an overly broad deny
rule, for example) pushed everywhere at once can cause a fleet-wide outage in the time it takes
the control plane to distribute the update, which is why staged rollout applies to policy
changes, not just to the initial mesh adoption.
Provide a Redis Lua script (or clear pseudocode) that implements a distributed sliding-window rate limiter per key with parameters (limit, window_seconds). The script must atomically record the current request timestamp, remove expired entries, and return the current count and remaining allowance. Explain how TTL is used and how to call the script with EVALSHA for performance.
Sample Answer
Approach
Use a Redis sorted set per rate-limit key, where each member is one request and its score is the request's timestamp in milliseconds. A single Lua script prunes expired entries, counts what remains, and conditionally records the new request, all atomically, since Redis runs an entire Lua script as one uninterruptible operation. This is illustrative Lua meant to be loaded with SCRIPT LOAD and called via EVALSHA; it was not executed against a live Redis instance in producing this answer, so no run transcript is claimed below, only the script and its documented return shape.
-- KEYS[1] = rate limit key, e.g. "ratelimit:{user_id}"
-- ARGV[1] = limit (max requests allowed in the window)
-- ARGV[2] = window_seconds
-- ARGV[3] = current timestamp in milliseconds
-- ARGV[4] = unique request id (avoids collisions when two requests land in the same millisecond)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window_ms = tonumber(ARGV[2]) * 1000
local now = tonumber(ARGV[3])
local member = ARGV[3] .. "-" .. ARGV[4]
-- 1. Drop entries older than the window, atomically within this script
redis.call("ZREMRANGEBYSCORE", key, 0, now - window_ms)
-- 2. Count what remains in the window before adding this request
local current = redis.call("ZCARD", key)
if current < limit then
-- 3. Record this request's timestamp
redis.call("ZADD", key, now, member)
-- 4. Bound memory: the key should not outlive the window
redis.call("PEXPIRE", key, window_ms)
return {1, current + 1, limit - current - 1}
else
-- refresh TTL even on rejection, so an idle-but-full key doesn't linger forever
redis.call("PEXPIRE", key, window_ms)
return {0, current, 0}
end
Return shape: {allowed (1 or 0), count_after, remaining}.
Key points
- Atomicity: Redis executes an entire Lua script as a single atomic operation, no other command from any client can interleave between the prune, the count, and the write. Doing count-then-decide-then-write as three separate round trips would race under concurrency: two requests could both read a count of 9 against a limit of 10, both decide "allowed," and both write, letting 11 through.
- Why a sorted set: scoring each member by its millisecond timestamp turns
ZREMRANGEBYSCORE key 0 (now - window)into an exact sliding-window prune in one command, andZCARDafter pruning gives the true count of requests strictly inside the current window, with no approximation. This is why it is called a sliding-window-log algorithm, in contrast to a fixed-bucket counter that resets on a clock boundary and can let roughly twice the limit through right at the boundary edge. - Unique member per request: using the timestamp alone as the sorted-set member risks silently deduplicating two requests landing in the same millisecond under high queries-per-second (QPS) traffic, since sorted-set members must be unique. Appending a request ID avoids undercounting.
TTL usage
PEXPIRE key window_ms runs after every write, bounding the key's lifetime to roughly one window past the most recent request, so a client that stops calling entirely has its rate-limit key evicted automatically instead of sitting in memory forever. Refreshing it even on a rejected request keeps a currently-active, currently-abusive key alive for enforcement, while a truly idle key still expires on schedule.
Calling with EVALSHA for performance
EVAL sends the full script text over the wire on every call. SCRIPT LOAD uploads the script once and returns a SHA1 hash; every subsequent call then uses EVALSHA <sha> <numkeys> <key> <args...>, sending only the hash plus arguments, avoiding re-parsing and re-transmitting the script body on the hot path. Production clients typically call EVALSHA optimistically and fall back to a one-time EVAL (which also re-registers the script) if Redis responds with a NOSCRIPT error, for example after a Redis restart flushed the script cache, so the limiter survives a cache eviction without requiring a deploy.
Complexity
ZREMRANGEBYSCORE and ZADD are each O(log N + M), where N is the sorted set's size and M is the number of expired entries removed in this call. Since M is naturally bounded by the limit itself, you can never accumulate more entries than the limit allows, and each is removed exactly once, the amortized cost per call stays close to O(log limit), cheap even for a very hot key.
Edge cases
Clock skew across application servers, each independently supplying ARGV[3], can make the window fuzzy across servers; mitigate by having the script call Redis's own TIME command internally instead of trusting each caller's clock, trading a small amount of script complexity for correctness. A single extremely hot key, one abusive caller hammering the limiter, still funnels through Redis's single-threaded command execution, so a very high-QPS attacker against one key can itself become a load concern, worth pairing this with a coarser upstream limit. A retried or timed-out EVALSHA call must not double-count; treat the rate-limit check as best-effort-once per logical request, not blindly retried.
Trade-offs & pitfalls
Fixed-window counters are simpler and cheaper but let up to roughly twice the limit through at window boundaries, the exact boundary-burst problem this sliding-window-log design specifically avoids. A token-bucket algorithm is a common alternative that better supports controlled bursts, at the cost of a different mental model to reason about. This sorted-set approach uses O(limit) memory per key, one entry per request currently in the window, versus a fixed-window counter's O(1), a real cost at very high per-key limits.
What is an API gateway, and what security responsibilities does it typically take on for the services sitting behind it?
Sample Answer
Direct answer
An API gateway is a single entry point that sits in front of a set of backend services and handles cross-cutting concerns, routing, traffic management, and security, before a request ever reaches business logic. On the security side, its main value is enforcing cheap, universal checks exactly once, instead of every individual service having to duplicate that logic.
Structured elaboration
Typical security responsibilities a gateway takes on:
- Transport security: terminating TLS (Transport Layer Security) and enforcing HTTPS-only, including a minimum supported TLS version.
- Coarse authentication: validating a token's signature and expiry, or checking an API key, before forwarding the request at all.
- Coarse authorization: confirming this identity is allowed to call this route in general, not the fine-grained "does this caller own this specific record" check, which still belongs inside the service.
- Rate limiting and throttling: protecting the whole platform from abuse or an accidental traffic spike from any single caller.
- Request validation: rejecting malformed, oversized, or wrong-content-type requests early, before they cost any downstream service compute.
- IP allow/deny lists and basic filtering: blocking known-bad sources or request patterns before they reach anything meaningful.
- Centralized audit logging: one place to see every request that entered the system, useful for both security review and debugging.
- Identity propagation: injecting a verified identity (for example, a user-id header) into the forwarded request, so downstream services trust the gateway's verification rather than each re-parsing raw tokens themselves.
Worked example
A POST /orders request arrives with no authentication token. The gateway rejects it with a 401 response before the orders service, inventory service, or payment service ever see it, so none of them spend any compute on traffic that was never going to be allowed. A validly authenticated request for the same endpoint is forwarded along with an X-User-Id header the gateway added after verifying the token, so the orders service can trust that identity without re-validating the raw token itself.
Trade-offs & pitfalls
A gateway should take on responsibilities that are universal and cheap to check without business context, not fine-grained, per-resource authorization, which needs domain knowledge only the service actually has. The most common pitfall is treating the gateway as the only layer of defense: it is a first filter that removes obviously bad traffic early, not a substitute for a service independently checking that a specific caller is allowed to touch a specific resource.
That is every published API Security, Authentication and Authorization question for Cloud Engineer so far. Browse the other topics in this category, or practice this one interactively.