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.
Compare JWT-based stateless authentication and server-side session-based authentication stored in Redis. For a REST API expected to scale to millions of users, discuss trade-offs for scaling, token revocation, session invalidation, security implications of token theft, and complexity of implementation.
Sample Answer
Direct answer
JSON Web Token (JWT)-based authentication is stateless, the server verifies a signature and trusts the claims, no lookup needed, which scales trivially across regions but makes revocation genuinely hard. Redis-backed sessions are stateful, every request does a lookup against a shared store, which makes revocation and invalidation instant and simple but adds a shared-infrastructure dependency on every request. At millions-of-users scale, Redis itself scales horizontally well enough that "sessions don't scale" is largely a myth; the real decision driver is usually the revocation and security trade-off, not raw throughput capacity.
Structured elaboration
| Dimension | JWT (stateless) | Redis session (stateful) |
|---|---|---|
| Scaling | Any service instance or region can validate a token independently with just a public key, no shared store required | Every validating service needs network access to the same session store, a shared dependency and potential multi-region friction point, though Redis itself clusters and scales well within a region |
| Token revocation | Genuinely hard: valid until its exp claim regardless of server-side wishes, unless you add an auxiliary deny-list, which reintroduces a lookup and partially undoes the statelessness benefit | Trivial: delete the session key, the very next request fails, revocation is instant |
| Session invalidation (logout, force-logout-all-devices) | Awkward: no central list of a user's active tokens to invalidate, needs the same kind of deny-list or versioning workaround as revocation | Straightforward: delete one key for a single device, or every session key for a user across all devices |
| Security implications of token theft | A stolen token is fully usable for its entire remaining lifetime with no way to cut it off except waiting for expiry or standing up deny-list machinery, which is why short JWT time-to-live values matter so much | A stolen session ID is equally usable until someone notices and deletes that key, at which point it is dead immediately, a faster, cleaner operational response |
| Complexity of implementation | Simple to verify, no store to manage, but a production-grade implementation converges toward needing similar infrastructure anyway to solve revocation | Conceptually simpler, one source of truth, at the cost of an operational dependency, Redis availability, backup, and memory sizing for millions of concurrent sessions |
Why "sessions don't scale" is largely outdated for typical API traffic: Redis session lookups are sub-millisecond and Redis clusters handle very high request rates comfortably, so raw throughput is rarely the actual constraint. Where JWTs genuinely win on scaling is avoiding a hard dependency on a shared store being reachable from every service, which matters in a multi-region or partially-connected topology, an availability and architecture concern, not a raw-throughput one.
Worked example
A user changes their password and expects to be logged out of every device immediately. With Redis sessions, this is a single operation: delete every session key associated with that user ID, done. With pure stateless JWTs, there is no server-side list of that user's outstanding tokens to delete; achieving the same guarantee requires bolting on exactly the kind of server-side state, a "tokens issued before time T for user U are invalid" version counter checked on every validation, that a session store gave you for free. This is the concrete moment the "JWT avoids server state" argument breaks down against a real product requirement.
Trade-offs & pitfalls
"JWT is always better because it's stateless" is a reflexive answer worth correcting on sight; it ignores exactly the revocation gap shown above. A common production pattern actually blends both: a short-lived JWT as the access token for fast, low-overhead per-request validation, backed by a Redis-tracked refresh token or session for the parts that genuinely need instant revocation. This mirrors a broader pattern worth naming: pair a fast, stateless credential for the common case with a narrow, stateful mechanism reserved specifically for the cases that genuinely need instant revocation. Picking pure stateless JWTs for a product with a hard "force logout everywhere" requirement, banking or healthcare, without planning the revocation story up front is a design mistake that tends to surface painfully late, usually during an actual incident.
You're asked to architect a zero-trust setup for a hybrid environment exposing APIs to internal services, external partners, and mobile clients. Cover identity and access management, device posture checks, short-lived credentials, conditional access policies, service identity, TLS/mTLS, API gateway and enforcement points, and how to support clients in intermittent connectivity scenarios.
Sample Answer
Direct answer
A zero-trust design for a hybrid environment treats every caller, whether it is an internal
service, an external partner, or a mobile client, as untrusted by default and re-verifies
identity and context on every request, rather than granting broad access once a caller is
"inside the network." The core building blocks are strong identity for both users and services
(short-lived credentials instead of static secrets), device and context signals evaluated at
request time (conditional access), and a small number of well-defined enforcement points (an API
gateway plus per-service checks) that consistently apply policy across on-prem, multiple clouds,
and partner networks rather than each environment inventing its own rules.
Structured elaboration
Identity and access management. Every principal (human user, service, and partner
integration) needs a durable, centrally-managed identity, issued by a single identity provider
(or federated set of providers) rather than per-environment local accounts. Users authenticate
via OpenID Connect (OIDC, an identity layer built on OAuth 2.0) with the resulting token carrying
verified claims about who they are; services authenticate to each other using workload identity
(certificates or signed tokens tied to what the service is, not a shared secret it holds).
Device posture checks. For flows that originate from a device (a partner's engineer, a
mobile client), the access decision should factor in device state; whether it is managed,
patched, has disk encryption enabled, is not jailbroken/rooted, rather than trusting the network
location it is calling from. A request from a fully-patched, managed corporate laptop and one
from an unknown personal device should not receive the same default trust even if both present a
technically valid user token.
Short-lived credentials. Replace long-lived static API keys and passwords with
short-lived, automatically renewed tokens wherever the calling pattern allows it: OAuth access
tokens with lifetimes of minutes to a couple of hours, backed by refresh flows, and short-lived
certificates for service identity (paired with automated rotation, discussed further under
mTLS). A short lifetime bounds how long a leaked credential remains useful even if revocation is
delayed.
Conditional access policies. Access decisions combine identity, device posture, and context
(is this request from a location or pattern consistent with the user's history, is it during a
plausible time window, is the requested action unusually sensitive) into a policy evaluated at
request time, not just at login time; a session that started low-risk can be required to
step up (re-authenticate, add MFA) if its behavior later looks anomalous.
Service identity and enforcement points. Between services, mTLS (mutual TLS, where each side
proves its identity with a certificate) paired with a workload-identity standard like SPIFFE (Secure Production Identity Framework For Everyone, a standard for automatically issuing short-lived, verifiable identities to services)
gives every service a verifiable identity independent of network location, which matters
specifically because "hybrid" means you cannot rely on network topology (a private subnet, a VPN
tunnel) as a trust signal the way a single-cloud, single-network design sometimes does. An API
gateway sits at the edge for external and partner traffic as the primary enforcement point for
coarse-grained checks (authentication, rate limiting, gross authorization); internal
service-to-service calls enforce their own finer-grained authorization at each service, since a
single central gateway cannot see or safely encode every service's specific business rules.
Trust boundaries across on-prem, multi-cloud, and partner networks. Segment the environment
into explicit trust zones rather than assuming one flat network, and make every crossing between
zones go through an identity-checked enforcement point instead of being implicitly allowed by
network routing. For the cloud-side permissions surface specifically, CIEM (cloud infrastructure
entitlement management, tooling that inventories and right-sizes the often-sprawling permissions
granted to cloud identities and roles) is the practical mechanism for keeping the
identity-to-permission mapping from drifting into over-privilege as the environment spans
multiple cloud providers, each with its own IAM (Identity and Access Management, the system that governs who or what can access cloud resources) model; operationalize boundary changes with
automated testing (a policy change is deployed through the same CI/CD pipeline as code, with
automated checks that a proposed change does not accidentally widen an existing trust boundary)
rather than manual firewall-rule edits.
Supporting intermittent connectivity. Mobile and some partner clients cannot always reach the
identity provider or policy decision point in real time. Support this with short-lived cached
decisions (a token that remains valid, and whose validity can be checked locally via signature,
for a bounded window even if the issuer is briefly unreachable) rather than a hard requirement
that every single request round-trip to a live policy check; balance that against the same
short-lived-credential principle by keeping the cached window short enough that a revoked
credential is not usable for long after revocation.
Worked example
graph TD
C1[Internal Service] -->|mTLS plus SPIFFE identity| GW[API Gateway - PEP]
C2[External Partner] -->|OAuth token plus mTLS| GW
C3[Mobile Client] -->|short lived token plus device posture| GW
GW --> PDP[Policy Decision Point]
PDP -->|check identity, posture, context| IAM[Identity Provider]
IAM --> PDP
PDP -->|allow or deny| GW
GW --> BE[Backend Services]
The gateway is the policy enforcement point (PEP): it never decides trust on its own, it forwards
the identity and context signals it collected (which credential type, what device posture, what
network origin) to the policy decision point (PDP), which consults the identity provider and
returns an allow or deny. This separation is what lets the same enforcement logic apply
consistently whether the caller is an internal service crossing a cloud boundary, an external
partner, or a mobile client on an unreliable connection: the PEP's job (collect signals, forward,
enforce the verdict) does not change per caller type, only the specific signals available do.
Trade-offs and pitfalls
- A central policy decision point can become a latency and availability bottleneck. Cache
decisions locally at the enforcement point for a short, explicitly bounded window to absorb
brief PDP unavailability and reduce round-trip latency, rather than either making every request
block on a live PDP call or letting enforcement points make their own independent (and
potentially inconsistent) decisions. - Zero trust does not mean zero trust in the credential's assertions once verified, it means
not trusting network location as a substitute for verification; conflating the two leads teams
to over-engineer per-request re-verification for signals (like a properly-scoped, freshly-issued
short-lived token) that were already strongly verified moments earlier. - Common wrong turn: building the identity and policy layer first and treating multi-cloud
CIEM as a later cleanup task. Cloud IAM permissions sprawl quickly and independently of your
application-level zero-trust design; without CIEM-driven visibility from early on, the
"authorized" answer your policy layer gives can still be sitting on top of drastically
over-permissioned cloud roles underneath it. - Common wrong turn: applying identical policy rigor to every zone. A partner integration
handling low-sensitivity data does not need the same conditional-access strictness as an
internal service touching customer financial records; uniform maximum strictness everywhere
usually just produces friction that teams route around, rather than better security.
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.
Propose a policy and system to rotate and revoke long-lived API keys across 10,000 services and 1,000,000 keys with minimal client disruption. Describe centralized vs decentralized rotation, key versioning in the backend, publish/subscribe propagation of revocations, client notification strategies, and emergency revocation mechanisms.
Sample Answer
At this scale, rotation and revocation need two separate paths. A slow, scheduled, centrally-governed rotation path spreads renewal load evenly so no client hits a re-provisioning cliff, and a fast, decentralized emergency-revocation path propagates a deny-list to every enforcement point in well under a minute, independent of the normal rotation cadence.
Architecture
- Central Key Management Service (KMS) / policy plane: owns key metadata, versioning, audit trail, and issues new key versions.
- Publish/subscribe event bus (a durable, replicated event log such as Kafka): carries
KEY_CREATED,KEY_DEPRECATED, andKEY_REVOKEDevents. - Distributed enforcement: API gateways or sidecars at each of the 10,000 services keep a locally-cached deny-list refreshed via the event bus, so the request hot path never makes a synchronous call to the central KMS.
flowchart LR
KMS[Central Key Management Service] -- publishes --> Bus[(Pub/Sub Event Bus)]
Bus -- KEY_REVOKED event --> GW1[Gateway A]
Bus -- KEY_REVOKED event --> GW2[Gateway B]
GW1 -- local deny-list lookup --> SvcA[Services Group A]
GW2 -- local deny-list lookup --> SvcB[Services Group B]
GW1 -. periodic reconciliation poll .-> KMS
Key versioning
Every logical key has immutable versions, key_id:v1, key_id:v2, and so on. Metadata tracks status (ACTIVE, DEPRECATED, REVOKED), creation time, and a grace-period expiry. Rotation creates vN+1 as ACTIVE, marks vN DEPRECATED with an overlap window, then flips vN to REVOKED once that window closes.
Centralized vs decentralized rotation
Central, scheduled rotation is predictable and easy to audit, but the control plane must stay highly available at this scale. Decentralized, delegated rotation, where a service requests its own rotation via a signed delegation token, reacts faster to local conditions but needs strict policy bounds so a compromised service can't mint arbitrary keys. In practice, a hybrid works best: a central schedule for routine rotation, plus delegation tokens for services that need to self-rotate within policy limits.
Propagation
Each event carries key_id, new_version, action, a sequence_number, and a signature, so gateways can apply updates idempotently and reject replayed or spoofed events. Because pub/sub delivery is only "eventually" reliable in practice, a periodic reconciliation poll against the central store (for example, every 30 seconds) acts as a safety net for any gateway that missed an event.
Client notification
Passive: SDKs read the active and previous key version at startup and accept both during the grace window. Active: a webhook or push notification to registered service owners at rotation time, and again shortly before the grace window closes. Fallback: sidecars poll the KMS on a backoff schedule if their event-bus connection drops.
Emergency revocation
An admin or automated detector triggers a REVOKE, published on a high-priority channel ahead of routine rotation traffic. Gateways apply it to their local deny-list immediately on receipt, a hash-set or Bloom filter lookup (a compact, probabilistic structure that can quickly say "definitely not revoked" or "maybe revoked," trading a small false-positive rate for speed and low memory), so no round trip is added to the request path. Any client not currently connected to the bus is still caught by the periodic reconciliation poll, which bounds the worst-case exposure window to the poll interval rather than to the routine rotation cadence.
Worked example
Fleet size: 1,000,000 keys across 10,000 services, an average of 100 keys per service. Under a 90-day max-age rotation policy, spreading rotations evenly means rotating 1,000,000 / 90 ≈ 11,112 keys per day, avoiding the rotation storm a naive "rotate everything on day 90" policy would cause. The grace window is 72 hours, so a service that only reads its key at deploy time still has three days to pick up the new version before the old one is revoked. On the emergency path, if the reconciliation poll runs every 30 seconds, the worst-case time from "revoke pressed" to "rejected at every one of the 10,000 gateways" is bounded by that 30-second interval, completely independent of the 72-hour grace window used for routine rotation.
Trade-offs and pitfalls
Rotating too aggressively burns client engineering time on re-provisioning; too rarely widens the compromise blast radius, the 90-day and 72-hour figures above are a starting policy to tune against your own data sensitivity, not a fixed rule. A fully centralized design is simple to audit but makes the KMS a single point of failure at 1,000,000-key scale, hence the pub/sub fan-out to cached edge deny-lists instead of a synchronous per-request call. Bloom filters give O(1) local revocation checks with a tunable false-positive rate, but that rate must be sized (or backed by a periodic exact-set refresh) so it never wrongly allows a revoked key, only, at worst, wrongly flags a small number of valid ones for a slower fallback check. A common wrong turn is relying on pub/sub alone with no polling backstop, a single dropped message then leaves a revoked key valid indefinitely at that node.
A B2B customer integration needs strong authentication, auditability, and occasional offline batch transfers. Compare OAuth2 (confidential clients), mutual TLS, API keys, and JWT-based approaches for this scenario. Outline token lifecycle, rotation, scopes/least-privilege, revocation strategies, and developer ergonomics for each approach.
Sample Answer
No single mechanism wins outright here: use mutual TLS (mTLS) for strong machine identity at the transport layer, layered with OAuth 2.0 confidential-client tokens (JWTs, issued to a "confidential client": a client, like a backend server, that can securely hold a secret, unlike a mobile app or single-page app that can't) for fine-grained, auditable scopes, and treat plain API keys as the fallback for only the lowest-risk pieces of the integration.
Comparison across the four approaches
| Approach | Token lifecycle | Rotation | Scopes / least privilege | Revocation | Developer ergonomics |
|---|---|---|---|---|---|
| OAuth 2.0 (confidential client) | short-lived JWT access token plus a longer refresh token | rotate client secret periodically; rotate refresh token on use | scopes embedded in token, enforced by resource server | revoke refresh token, short access-token TTL bounds exposure | standard libraries, works for both online calls and scheduled batch renewal |
| Mutual TLS (mTLS) | a certificate presented per TLS connection, no separate token | certificate rotation via public key infrastructure (PKI), often automated | authenticates identity only; combine with a policy layer for fine-grained scopes | a certificate revocation list (CRL, a periodically-published list of revoked certificate serial numbers) or the Online Certificate Status Protocol (OCSP, a live per-certificate revocation check against the issuer) | strong security, heavier certificate-management burden on both sides |
| API keys | static, often long-lived | manual or scheduled | coarse; can map a key to a role but not fine-grained by default | immediate server-side disable, but no standard introspection protocol (a live network check with the issuer asking "is this credential still valid right now") | easiest to implement, weakest security, acceptable only for low-risk pieces with extra controls |
| JWT-based (self-contained) | short-lived recommended, verified locally via signature | rotate signing keys via published key IDs | scopes and roles embedded as claims, fine-grained and auditable | hard to revoke a stateless token early, mitigate with short TTL plus a revocation list for exceptions | very usable, especially for offline batch, since verification needs no live network call |
Why layer mTLS with OAuth for this scenario
mTLS answers "is this really the partner's server calling me" at the network layer, independent of any application-level credential, which matters for occasional offline batch transfers where a job might run without a live interactive session to refresh a token against. OAuth 2.0 confidential-client JWTs answer "what is this specific call allowed to do" with fine-grained, auditable scopes, something mTLS alone doesn't express. Plain API keys are kept only for the lowest-risk pieces (for example a status-check endpoint with no sensitive data) because they lack native scoping and revocation tooling.
Worked example
A partner runs a nightly batch job, no interactive user, sometimes running from a machine with intermittent connectivity, that uploads a settlement file. The batch job authenticates via mTLS using a certificate valid for 90 days, auto-renewed by an internal PKI service 30 days before expiry, so a job running mid-renewal still has a valid certificate. At call time it also presents a Client Credentials-issued JWT access token, TTL 15 minutes, scope settlements:write only. Because the JWT is self-contained and locally verifiable, the batch job can check its own token's expiry before starting an upload and pre-fetch a fresh one if needed, without a live back-and-forth handshake beyond the initial token request. That's the property that makes JWTs a good fit for the "occasional offline batch" requirement, compared with an opaque token that would need a live introspection call on every use.
Trade-offs and pitfalls
Requiring mTLS for every integration adds real onboarding cost, certificate issuance, renewal automation, partner-side competence, so reserve it for partners handling sensitive data or high transaction value rather than every low-risk integration. JWTs are convenient for offline batch specifically because they don't need a live call to verify, but that same property makes early revocation hard, keep the TTL short enough that a compromised token has a small blast radius even without a live revoke path. API keys are tempting for their simplicity but the weakest option across every dimension in the table above; if you must use them, add compensating controls (IP allowlisting, mandatory rotation, no export of cleartext after creation).
Unlock Full Question Bank
Get access to all 25 API Security, Authentication and Authorization interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.