In a ZAP automation plan, what does a context's authentication block hold and what do its users entries hold?
answer
- one block is how, the other is who
- credentials never live in the method
- two different curly-brace syntaxes in one line
- ${VAR} at plan load, tokens at replay
basics
~20 sA context's authentication block says how to log in: the method plus its parameters, such as the login request URL and body. The users list says who logs in, each entry carrying a name and a username and password.
solid answer
~40 sThey are deliberately separate. `authentication` picks a `method` (`form`, `json`, `http`, `script`, `manual`, or one of the `authhelper` add-on's) and supplies the `parameters` that method needs, with the identity left out. `users` is a list of `{name, credentials}`, where `credentials` holds `username` and `password`. A job that should run logged in names a user by its `name`. The split means one configured login shape serves several identities, and it is why the login body carries `{%username%}` and `{%password%}` rather than a real credential. Note that `${VAR}` in a plan is resolved by the automation add-on when the plan is read, while `{%username%}` is resolved by the login method at replay - two syntaxes, two layers.
code
yaml · 15 linesenv:
contexts:
- name: app
urls:
- "https://example.com/"
authentication:
method: form
parameters:
loginRequestUrl: "https://example.com/login"
loginRequestBody: "user={%username%}&pass={%password%}"
users:
- name: scanner
credentials:
username: "${APP_USER}"
password: "${APP_PASS}"go deeper
Be able to point at the two blocks and say which one holds a password. The method describes the request; the users list supplies the identity that fills it in.
Explain the substitution timing: the automation add-on expands ${VAR} once when the plan is read, and the login method replaces the credential tokens on every login attempt, per user.
Show that you check the wiring end to end - a populated users list is worthless if the scanning job never names a user, and that combination runs clean and covers nothing behind the login.
The reusable decision is that login shape and identity are separate artefacts: one context definition, many identities, credentials injected from outside the plan file rather than edited into it.
## What the two blocks are for A ZAP automation plan is `env` plus `jobs`. Inside `env`, each entry of `contexts` can carry an `authentication` block and a `users` list, and they answer two different questions: - **`authentication` says HOW to log in.** It picks a `method` and supplies the `parameters` that method needs - for a `form` login, the URL to post to and the body to post. - **`users` says WHO logs in.** Each entry is a `name` plus a `credentials` map holding a `username` and a `password`. Neither block is useful alone. The method describes the shape of a login request with the identity left out; the users list supplies the identity with no idea how it is sent. A job that should run authenticated names a user by its `name`, and the context's method is what turns that name into a real request. ## What actually goes in `authentication` `method` is a string. `parameters` is a free-form map whose accepted keys depend on the method: `loginRequestUrl` and `loginRequestBody` for `form` and `json`, `hostname` and `realm` for `http`, `script` and `scriptEngine` for `script`. The block is **credential-free by design** - it is written once per application, and it does not change when you add a second test identity. ## What actually goes in `users` Each user is a `name` used by jobs, and a `credentials` map. For the `form` and `json` methods the credential keys are `username` and `password`, because those methods are backed by core's `UsernamePasswordAuthenticationCredentials`. A `script` method uses `GenericAuthenticationCredentials` instead, whose credential parameter names are whatever the script declares, so a script login can take a tenant id or an API key rather than a password. ## Two curly-brace syntaxes, resolved by different layers This is where the block split bites, because one line of YAML can carry both: ``` loginRequestBody: "user={%username%}&pass={%password%}" username: "${APP_USER}" ``` | syntax | who resolves it | when | |---|---|---| | `${NAME}` | the `automation` add-on's environment | when the plan is read, once | | `{%username%}` / `{%password%}` | core's form or JSON login method | at every login replay, per user | The automation add-on's variable pattern matches only a dollar sign followed by braces, so `{%username%}` passes through the plan untouched and survives into the configured method. That is deliberate: `${...}` is how a value gets **into** the plan from outside, and `{%...%}` is how a per-user value gets **into the login request** at replay time. They are not interchangeable, and swapping them produces a plan that is syntactically fine and functionally wrong. ## Why the split matters when a scan runs unattended 1. **Reuse.** One `authentication` block plus several `users` entries lets the same context be scanned as a low-privilege and a high-privilege identity without duplicating the login shape. 2. **Substitution timing.** Because credentials are resolved per user at replay, the same `loginRequestBody` serves every identity. Putting a literal password in the body instead collapses the plan to one identity and bypasses the token machinery entirely. 3. **Externalisation.** Because `credentials` values support `${...}`, the actual secret can be supplied to the plan from outside it rather than living in the file that is checked in. 4. **Job wiring.** A job's optional `user` parameter must name a user defined in the environment - a job that names a user the context does not declare has nothing to authenticate as. ## The failure this split prevents, and the one it does not It prevents a plan where the identity is fused into the request shape, which is the version that cannot be scanned twice as two different people. It does **not** prevent a plan with a perfectly good `users` list and an `authentication` block whose body never mentions the tokens - in that case every user posts the same static string and the login is effectively anonymous. Nothing in the method's own configuration check looks for the tokens, so this is a silent outcome rather than a reported one. ## Reading a plan quickly When you inherit a plan and want to know whether it really logs in, read the two blocks together and check, in this order: - does `authentication.method` name a method this installation actually has; - do its `parameters` describe a request that would genuinely log the application in; - does `loginRequestBody` contain both credential tokens; - does `users` carry an entry whose `credentials` are populated; - does the scanning job name that user. Any one of those missing produces a run that completes normally and covers only the pages a stranger can see. None of them produces an error on its own, which is why reading the pair of blocks is a habit rather than a debugging step.
- Why does the automation add-on's variable substitution not eat the credential tokens in the same string?Its variable pattern matches only a dollar sign followed by braces. `{%username%}` does not match it, so the add-on leaves it in place when it reads the plan, and core's login method replaces it later at replay time. The two syntaxes coexist in one line on purpose.
- How does a scanning job say which identity to scan as?Jobs that support authentication take an optional `user` parameter whose value must name an entry in the context's `users` list. A job that omits it runs anonymously, however correct the context's authentication block is - which is a common reason an otherwise well-configured plan produces unauthenticated coverage.
saying these in an interview costs you the question
- Writes the real password into loginRequestBody instead of the users list
- Thinks ${VAR} and {%username%} are the same substitution
- Says the users list selects the authentication method
- Expects a plan to log in with no users entry defined
- Believes credentials are declared per job rather than per context