skip to content

In a ZAP form login, what do the tokens in loginRequestBody do, and which methods actually honour them?

level: middleimportance: should knowfreq 60%

answer

  1. placeholders, filled in per user at replay
  2. only the two body-posting methods honour them
  3. a literal replace, not a template engine
  4. the body encoder differs from the URL encoder

basics

~20 s

They are credential placeholders. Core's form-based and JSON-based login methods replace them with the current user's username and password just before sending the login request, so one configured request serves every identity in the context.

solid answer

~40 s

`{%username%}` and `{%password%}` are placeholders you write into the login request; the method substitutes the current user's values at replay. Only **two** registered method types do this - form-based and JSON-based - because both extend `PostBasedAuthenticationMethodType`, which is where the pair is declared. `http` has no body you author, `manual` sends no login request, `script` gets the credentials as an object and interpolates them itself. The substitution is a plain literal string replace, and the **encoder differs**: form URL-encodes the value into a form body, JSON escapes it for a JSON string, and a token in the login *URL* is always URL-encoded whichever method you chose. Nothing checks that the tokens are present, so a misspelled one is posted verbatim.

code

yaml · 5 lines
yaml
# form method: the value is URL-encoded into the body
loginRequestBody: "user={%username%}&pass={%password%}"
---
# json method: same tokens, JSON-escaped instead
loginRequestBody: '{"user":"{%username%}","pass":"{%password%}"}'

go deeper

for a junior

Know that the login body holds placeholders rather than a real password, and that the tool swaps in the current user's values before it sends the request.

for a middle

Explain that the substitution is a literal replace performed by the form and JSON methods only, and that each applies its own encoder to the value it substitutes.

for a senior

Demonstrate that you verify by reading the request that was actually sent, because a wrong token or a wrong encoder produces a normal-looking failed login and no error.

for a principal

Worth standardising: capture a real login request and derive the configured body from it, rather than hand-writing bodies, so that encoding and method choice cannot drift apart across teams.

## What the tokens are `{%username%}` and `{%password%}` are **credential placeholders**. You write them into the login request you configure, and the login method replaces them with the current user's values just before it sends that request. They exist so that one configured login request serves every test identity in the context. They are worth being precise about, because they are **composed, not written**, in the source. Core declares a token prefix and a token postfix on `AuthenticationMethod`, and `PostBasedAuthenticationMethodType` concatenates each with a fixed middle word to produce the two patterns. A literal search of core for `{%username%}` finds nothing - which is a good reminder that failing to find a string is not evidence that the tool does not ship it. ## Which methods honour them `PostBasedAuthenticationMethodType` is the shared base class of exactly two registered method types: **form-based** and **JSON-based**. Those are the two that substitute the tokens into a login request. - **`http`** has no request body you author, so there is nothing to interpolate. - **`manual`** reuses a session you selected by hand; it sends no login request at all. - **`script`** receives the credentials as an object passed into its `authenticate` method and does its own interpolation in code, against credential parameter names the script itself declares. - The `authhelper` methods type credentials into a browser rather than into a string. So "ZAP substitutes `{%username%}`" is too broad. **Core's form and JSON login methods substitute it into the login request**; the token grammar itself is shared more widely, but this particular pair of names belongs to those two methods. ## Where the substitution happens, and with which encoder This is the part that surprises people. The substitution is a **plain literal string replace** - not a regular expression, not a templating engine - applied in two places with two different encoders: | part of the login request | encoder applied to the credential | |---|---| | the login URL | always URL-encoding | | the request body, form-based method | URL-encoding, into `application/x-www-form-urlencoded` | | the request body, JSON-based method | JSON string escaping, into `application/json` | The URL half is hard-wired: a token in `loginRequestUrl` is URL-encoded whichever method you chose. Only the **body** encoder varies by method, and it varies because the form and JSON methods pass different encoder functions into the shared base class. That is why picking the wrong method is not just a header mistake. Configure a JSON API login as `form` and a password containing an ampersand or a plus sign is URL-encoded into a JSON document that the server then reads literally - the credential that arrives is not the credential you stored. Configure a form login as `json` and a password containing a backslash or a quote is JSON-escaped into a form body, with the same result. ## What is never checked The method's own configuration check asks only whether a login request URL is set, and - for the JSON method, which requires a body - whether the body is non-empty. It does **not** check that either token appears anywhere. Consequences: 1. A body with a misspelled token is a valid configuration. 2. It is replaced with nothing, because a literal replace of a string that is not present is a no-op, so the misspelled token is posted verbatim. 3. A body with no tokens at all posts the same static string for every user in the context. None of those three is reported by the login step itself. Detecting them is the job of the separate verification configuration, which is a different mechanism and configured elsewhere. ## Getting it right first time Write the body by copying a real login request you have already captured, then replace the two credential values in it with the tokens - rather than typing the body from memory. That preserves every other parameter the application expects, keeps the content type consistent with the method you picked, and makes a wrong token visible as a value that does not look like the others. Then read the request the tool actually sent back out of the recorded history: the substituted body is right there, and it is the only place that tells you, without ambiguity, what arrived at the application.

  • Why does searching core's source for the literal token text find nothing?
    Because it is composed rather than written. Core declares a token prefix and postfix on `AuthenticationMethod`, and the post-based method type concatenates each with a fixed word to build the two patterns. The shipped behaviour is real; only the literal string is absent - a good reminder that one failed search is not an absence proof.
  • What happens if the password contains a character the chosen method's encoder treats specially?
    It is encoded per that method, which is correct only if the method matches the body. A JSON login misconfigured as `form` URL-encodes an ampersand or a plus sign into a JSON document, so the value the application parses is not the value you stored - and the login fails for a reason that looks nothing like an encoding problem.
  • Can a script-based login use the same token names?
    Not for its login request. The script receives a generic credentials object and the credential parameter names it declared, and builds the request in code. The token grammar is shared core-wide, but the fixed username and password pair belongs to the form and JSON methods.

saying these in an interview costs you the question

  • Says every authentication method substitutes the credential tokens
  • Calls the substitution a regex or template expansion
  • Assumes the method validates that both tokens are present
  • Thinks form and JSON differ only in the content-type header
  • Expects a misspelled token to raise a configuration error