skip to content

In an OpenAPI document, how does operation-level security interact with the root security block?

level: middleimportance: must knowfreq 55%

answer

  1. Two positions in the document, not three
  2. The more specific one wins outright
  3. Nothing is merged between them
  4. An empty list is a statement, not silence
  5. An empty object inside the list means something else

basics

~20 s

A root-level OpenAPI security block is the default for every operation. An operation's own security field replaces that default entirely rather than adding to it, and an empty array removes the inherited requirement, making the operation public.

solid answer

~40 s

`security` at the document root declares the requirements that apply to every operation by default. An Operation Object may carry its own `security`, and that **overrides** the root list completely — the two are never merged, so an operation listing one scheme does not also inherit the root's. Two special values matter. `security: []` on an operation, an empty array, removes the inherited requirement and makes the operation unauthenticated; this is how login, token, health and webhook-callback endpoints are declared. `security: [ {} ]`, an array containing an empty requirement object, makes authentication **optional**: an anonymous caller is acceptable alongside the other listed alternatives. Note that `security` exists only at the root and on operations — the Path Item Object has no `security` field, so there is no per-path default to hoist to.

code

yaml · 28 lines
yaml
security:
  - bearerAuth: []          # default for every operation

paths:
  /auth/token:
    post:
      summary: Issue a token
      security: []           # public - cannot require the token it issues
      responses:
        '200':
          description: Token issued
  /feed:
    get:
      summary: Feed, richer when authenticated
      security:
        - bearerAuth: []
        - {}                 # authentication optional
      responses:
        '200':
          description: Feed
  /admin/users:
    get:
      summary: Admin listing
      security:
        - adminKey: []       # replaces bearerAuth, does not add to it
      responses:
        '200':
          description: Users

go deeper

for a junior

Know that a root-level security block sets the default for all operations and that an operation can override it with its own.

for a middle

Explain that the override is a full replacement rather than a merge, and distinguish an empty array (public) from an array holding an empty object (optional) and from omitting the field (inherit).

for a senior

Show the review discipline: justify every opt-out, read the resolved per-operation requirement rather than reasoning about inheritance, and verify the gateway agrees with the document.

for a principal

Own how a path-wide rule is enforced when the format offers no path-item security — generation, linting, or contract tests — so no new operation silently ships without a requirement.

## Two places, one field The `security` field appears in exactly two positions in an OpenAPI 3.x document: at the **root**, where it sets the default for the whole API, and on an **Operation Object**, where it applies to that operation. It does **not** exist on the Path Item Object — so you cannot declare "everything under `/admin` needs the admin scheme" in one place. That absence surprises people who expect the inheritance model of parameters, which do have a path-item level. ## Override, not merge The rule is replacement. If the root requires `bearerAuth` and an operation declares `apiKeyAuth`, then that operation requires the API key **and not** the bearer token. The root requirement does not survive underneath. This trips people who reason by analogy with parameters, which merge. If an operation genuinely needs both, it must list both — and in that case the shape of the list matters, because two entries in one requirement object mean *and* while two objects in the array mean *or*. ## The empty array: opting out `security: []` on an operation removes any inherited requirement. It is the only way to say "this one is public" in a document with a root-level default, and it is what you write on: - the login or token endpoint, which by definition cannot require the credential it issues - a health or readiness probe - public metadata endpoints - callbacks the API itself receives from a third party under a different trust model Omitting `security` entirely on the operation is **not** the same thing: an absent field means "inherit the root default", while an empty array means "require nothing". That one-character difference between `security: []` and no `security` key at all is the most common defect in this area, and it fails in the dangerous direction only half the time — the other half it silently marks a protected endpoint as public in the documentation and in generated clients. ## The empty requirement object: making auth optional A related and distinct construct is an **empty Security Requirement Object** — `{}` — appearing as an element of the array. Because the elements of a `security` array are alternatives, and one of the alternatives is "no requirement at all", a list holding both `bearerAuth` and `{}` declares that a caller **may** authenticate but need not. That is the correct declaration for an endpoint whose response differs for authenticated callers — a feed that shows more items when you are logged in, say — as opposed to one that is flatly public. It can be written at the root too, making optional authentication the API-wide default, which is almost never what you want. ## Interaction with tooling Documentation renderers use this resolution to decide whether to show a lock icon on an operation; Swagger UI will not send the credential to an operation whose effective requirement is empty. Client generators decide per-operation whether to attach a credential interceptor. Lint rule sets commonly assert that the root declares `security` and that every operation-level `security: []` carries a description justifying it, because an unexplained opt-out is exactly what an attacker looks for in a published spec. ## Reviewing this in practice The practical discipline: declare the strictest common requirement at the root, keep operation-level `security` rare and always deliberate, and treat every `security: []` as something that must be justified in review. Then check the resolved view — most tools can show the effective requirement per operation — rather than reading the YAML and reasoning about inheritance in your head. And remember throughout that this resolution describes what clients should send; it does not configure any gateway, so an operation documented as public and an operation actually served anonymously are two independent facts that must be kept in agreement by testing, not by the document alone.

  • What is the difference between omitting security on an operation and writing security: []?
    Omitting the field means the operation inherits the root-level default. An empty array explicitly removes that default and declares the operation unauthenticated. The two look almost identical in a diff and mean opposite things, which is why an empty array should always be accompanied by a description explaining why the endpoint is public.
  • How do you declare that authentication is optional rather than absent?
    Include an empty requirement object as one of the alternatives, alongside the real scheme. Since the array elements are alternatives and one of them requires nothing, an anonymous call satisfies the list. Use it for endpoints whose response is richer when authenticated. An empty array is different — it says no requirement exists at all.
  • Can you apply a security requirement to every operation under one path in a single place?
    No. The Path Item Object has no `security` field, so requirements are declared only at the document root and on individual operations. A path-wide rule has to be repeated on each operation under that path, which is why teams generate or lint that repetition rather than trusting reviewers to spot a missing entry.

saying these in an interview costs you the question

  • Thinks operation security merges with the root list
  • Treats a missing security field as making an endpoint public
  • Confuses an empty array with an empty requirement object
  • Expects a path-item level security field to exist
  • Believes the document's security block configures the gateway

context