Testland
Browse all skills & agents

zap-authenticated-scans

Configures authenticated DAST sessions in ZAP - ZAP Context + Authentication Method (form, JSON, script, browser-based, HTTP/NTLM), Session Management strategy (cookie, header, script), Verification Strategy (regex indicators, poll-URL), CSRF token handling, OAuth/bearer header injection, logged-in/logged-out indicator calibration, and context XML export for use with `-n` in baseline and full scans. Use when the team needs DAST coverage of authenticated routes - the most common DAST gap and the hardest DAST setup to get right.

Install with skills.sh (any agent)

npx skills add testland/qa --skill zap-authenticated-scans
View source

zap-authenticated-scans

Overview

Unauthenticated DAST scans cover only the public attack surface. For most apps, 70-90% of routes sit behind a login wall. This skill is a build-an-X workflow that walks the full authenticated session setup: ZAP Context creation, choosing the right Authentication Method, wiring Session Management, calibrating logged-in/out indicators, handling CSRF tokens, injecting OAuth/bearer headers, and exporting the context file for CI reuse.

Nearest neighbors and differentiation axes:

  • zap-baseline covers the -n context_file flag but not how to build that file.
  • dast-scan-cadence-author covers cadence (PR-blocking vs. nightly vs. release), not auth wiring.
  • Neither neighbor covers CSRF handling, bearer injection, OAuth flows, verification-strategy calibration, or context XML export.

How to use

  1. Create a ZAP Context that includes the app URLs and excludes /logout, so the scanner does not log itself out mid-scan (Step 1).
  2. Choose the Authentication Method that matches the app's login mechanism - Form, JSON, HTTP/NTLM, Script, or Browser-Based (Steps 2-6).
  3. Set the Session Management method (cookie, HTTP header, or script) to match how the app tracks sessions (Step 7).
  4. Calibrate the Verification Strategy with logged-in and logged-out indicators so ZAP detects session expiry and re-authenticates (Step 9).
  5. Add one or more Users, injecting credentials via -config or env vars rather than hardcoding them (Step 10).
  6. Confirm auth in the Authentication Tester, then export the Context to .zap/context.xml (Step 11).
  7. Run the scan in CI with -n context.xml, or replay the same session in Burp for manual testing (Steps 11-12).

Step 1 - Create a ZAP Context

Per zaproxy.org/docs/desktop/start/features/authentication/ (opens in new window), authentication in ZAP is always scoped to a Context - a named set of URLs. Create one before touching any auth setting:

  1. Open ZAP desktop. In the Sessions dialog: Session Properties > Contexts > Add.
  2. Set the Context Name (e.g., myapp-auth).
  3. Set the Include pattern to cover all app URLs: https://app.example.com/.*
  4. Add exclude patterns for logout URLs to avoid the spider logging itself out during a scan: https://app.example.com/logout.*

All auth settings attach to this Context. CLI scans reference it via -n context.xml (Step 9).

Step 2 - Choose the Authentication Method

Per zaproxy.org/docs/desktop/start/features/authmethods/ (opens in new window), ZAP supports five built-in methods. Choose by app login mechanism:

App login typeMethod to use
HTML form POST with username + password fieldsForm-Based
JSON POST {"username":"...","password":"..."}JSON-Based
HTTP Basic / Digest / NTLM challengeHTTP/NTLM
Custom flow (OTP, magic link, multi-step)Script-Based
Modern browser-rendered SSO / OAuth redirectBrowser-Based (auth-helper addon)

Step 3 - Configure Form-Based Authentication

Per zap-methods (opens in new window), Form-Based auth requires:

  • Login URL: the POST endpoint (e.g., https://app.example.com/login)
  • Login Request POST Data: encodes credentials as URL params: username={⁠%username%}&password={⁠%password%} ZAP replaces {⁠%username%} / {⁠%password%} with User credentials at scan time. Never hardcode credentials in this field.
  • Username field and Password field names (match the HTML name attributes).

Per zap-methods (opens in new window), Form-Based auth supports re-authentication - ZAP detects session expiry and re-logs in automatically mid-scan.

CSRF token handling for form login: if the login form contains an anti-CSRF token field, configure its name in Tools > Options > Anti CSRF Tokens (per zap-auth (opens in new window)). ZAP fetches the login page, extracts the token, and replays it with the POST automatically.

Step 4 - Configure JSON-Based Authentication

Per zap-methods (opens in new window), JSON-Based auth is for apps whose login endpoint accepts a JSON body rather than form-encoded params:

  • Login URL: the POST endpoint
  • Login Request POST Data: {"username":"{⁠%username%}","password":"{⁠%password%}"}

ZAP sends Content-Type: application/json automatically. Supports re-authentication. Use this for REST API login endpoints returning a session cookie or JWT response body.

Step 5 - Configure Script-Based Authentication

Per zap-methods (opens in new window), Script-Based auth handles flows that Form-Based and JSON-Based cannot: OTP-augmented logins, multi-step forms, OAuth authorization-code flows with PKCE, or apps that rotate CSRF seeds on every page load. It requires the Script Console add-on and an Authentication script that builds the login request via helper.prepareMessage().

See references/script-based-auth.md for the Script Console setup, the Groovy authenticate() skeleton, and the OAuth authorization-code exchange pattern.

Step 6 - Configure Browser-Based Authentication (auth-helper addon)

Per zaproxy.org/docs/desktop/addons/authentication-helper/ (opens in new window), the Authentication Helper add-on provides Browser-Based Authentication for apps that use JS-rendered login pages, SSO redirects, or WebAuthn flows that headless HTTP clients cannot replay:

authentication:
  method: "browser"
  parameters:
    loginPageUrl: "https://app.example.com/login"
  verification:
    method: "autodetect"
sessionManagement:
  method: "autodetect"

ZAP launches Firefox, navigates to loginPageUrl, fills the username and password fields, and captures the resulting session token. The autodetect verification asks ZAP to find a suitable verification URL automatically.

Step 7 - Configure Session Management

Per zaproxy.org/docs/desktop/start/features/sessionmanagement/ (opens in new window), ZAP supports three session management methods. Set in Session Properties > Context > Session Management:

App session typeMethod
Session ID in a cookie (JSESSIONID, session, etc.)Cookie-Based Session Management
Authorization header (Basic, JWT Bearer)HTTP Authentication Session Management
Custom header or token rotationScript-Based Session Management

Per zap-session (opens in new window), Cookie-Based "session is being tracked through cookies" and tokens are imported from the HTTP Sessions Extension.

Per zap-session (opens in new window), Script-Based "is called whenever session management actions are performed" and requires the Scripts Console add-on.

Step 8 - Inject OAuth/Bearer Tokens via Environment Variables

Per zap-auth (opens in new window), ZAP exposes environment variables (ZAP_AUTH_HEADER_VALUE, ZAP_AUTH_HEADER, ZAP_AUTH_HEADER_SITE) for header-based injection of pre-obtained bearer tokens - OAuth client-credentials, API keys, CI-issued JWTs. Set them in the CI environment before the scan. For a full authorization-code exchange, use Script-Based auth (Step 5) instead and let ZAP manage token refresh.

See references/oauth-bearer-injection.md for the variable table and the CI Docker example.

Step 9 - Set Authentication Verification Strategy

Per zap-verify (opens in new window), ZAP uses an Authentication Verification Strategy to know whether a request runs as an authenticated user, driven by a Logged-In Indicator and a Logged-Out Indicator regex. Four strategies exist (Check Every Response, Check Every Request, Check Every Request or Response, Poll the Specified URL); calibrate the indicators by flagging logged-in and logged-out responses in the History tab.

See references/verification-strategy.md for the indicator examples, the strategy-selection table, and the calibration steps.

Step 10 - Add Users

Per zaproxy.org/docs/desktop/start/features/users/ (opens in new window), users are configured per-context at Session Properties > Context > Users > Add. Each user stores credentials that map to the Authentication Method's {⁠%username%} / {⁠%password%} placeholders.

Per zap-users (opens in new window): "Authentication Methods define the process; Users store the specific credentials needed for each user account." One context can hold multiple users (admin, read-only, unauthenticated) to test privilege separation in a single scan.

Never store plaintext credentials in the exported context XML committed to version control. Reference environment variables in CI (Step 8 pattern) or use ZAP's -config CLI flag to inject credentials at scan time:

zap-full-scan.py -t https://app.example.com \
  -n /zap/wrk/context.xml \
  -config context.users\(0\).name=scanner \
  -config context.users\(0\).credentials.username=$ZAP_USER \
  -config context.users\(0\).credentials.password=$ZAP_PASS

Step 11 - Export the Context XML

Once auth is confirmed working via the Authentication Tester (per zap-helper (opens in new window), under Tools > Authentication Tester or Ctrl+T), export the Context:

File > Export Context > save as context.xml

Commit context.xml to the repo at .zap/context.xml. The file encodes auth method, session management strategy, verification strategy, and include/exclude URL patterns. It does NOT contain user credentials when users are configured with the -config override pattern above.

Use in CI:

docker run --rm \
  -e ZAP_AUTH_USERNAME=$ZAP_USER \
  -e ZAP_AUTH_PASSWORD=$ZAP_PASS \
  -v $(pwd):/zap/wrk/:rw \
  ghcr.io/zaproxy/zaproxy:stable \
  zap-baseline.py -t https://app.example.com -n /zap/wrk/.zap/context.xml -J report.json

Per zap-baseline, the -n CONTEXT_FILE flag loads this file and activates authentication for the scan.

Step 12 - Replay with Burp Suite

For apps already configured in ZAP, mirror the session in Burp for manual testing by capturing a valid authenticated request via ZAP proxy, then:

  1. Export the HAR: right-click the authenticated request in ZAP History, Save as HAR.
  2. In Burp, import via Proxy > HTTP history > Import HAR.
  3. Set up a Macro (Project > Session handling rules > Macros) that replays the login POST and extracts the session token using a regex matching the cookie or JSON access_token field.
  4. Add a Session handling rule (Settings > Sessions > Session handling rules > Add) with scope covering the entire app and the macro set as the rule action.

This keeps Burp and ZAP scanning the same authenticated surface without re-configuring login from scratch in each tool.

Worked example

A team needs authenticated DAST coverage of a form-login SPA at app.example.com whose login page carries an anti-CSRF token.

  1. They create a Context myapp-auth including https://app.example.com/.* and excluding https://app.example.com/logout.* (Step 1).
  2. Login is an HTML form POST, so they pick Form-Based auth with POST data username={⁠%username%}&password={⁠%password%} and register the CSRF field name under Tools > Options > Anti CSRF Tokens (Steps 2-3).
  3. Sessions ride a JSESSIONID cookie, so they set Cookie-Based Session Management (Step 7).
  4. They flag a logged-in response containing href="/logout" and a logged-out Please log in page as indicators, using Check Every Response (Step 9, references/verification-strategy.md).
  5. They add a scanner user and export the Context to .zap/context.xml, with credentials supplied at scan time via -config (Steps 10-11).
  6. CI runs zap-baseline.py -t https://app.example.com -n /zap/wrk/.zap/context.xml -J report.json.

Result: the baseline scan authenticates, re-logs in when the session expires, and reports vulnerabilities across the routes behind the login wall instead of only the public pages.

Anti-patterns

Anti-patternWhy it failsFix
Skip context creation, use -u user:pass flagNo re-auth; spider logs out mid-scanContext + auth method (Steps 1-3)
Hardcode credentials in context.xmlSecrets leak in version control-config injection or env vars (Step 10)
No logged-out indicatorZAP reports false coverage on expired sessionsCalibrate both indicators (Step 9)
Form-based auth on a JSON-API loginZAP sends form-encoded body; app rejects itJSON-based auth (Step 4)
Exclude /login from context scopeAuth POST never proxied; ZAP can't authenticateInclude login URL; exclude only /logout (Step 1)
Browser-based auth without auth-helper addonmethod: browser is not a built-in; scan failsInstall Authentication Helper from Marketplace (Step 6)
Set verification strategy but no indicatorsStrategy is inactive; ZAP never detects re-auth needSupply at least one logged-in regex (Step 9)

Limitations

  • ZAP auth context cannot be built or tested without the ZAP desktop or automation framework; no pure-CLI context creation exists.
  • MFA (TOTP, SMS OTP) requires Script-Based auth with a TOTP library or a pre-generated token injected via env var; ZAP has no native MFA support.
  • Browser-Based auth requires a local browser and is not available in headless Docker without a virtual display or the auth-helper's browser-in-Docker mode.
  • Per zap-methods (opens in new window), Manual Authentication "does not support re-authentication in case the webapp logs a user out"; avoid for active scans longer than the session TTL.
  • Context XML export includes URL patterns but not user credentials when using the -config flag injection pattern; anyone needing credentials must supply them separately.

References

ZAP OAuth / Bearer token injection

View source (opens in new window)

ZAP OAuth / Bearer token injection

Per zap-auth (opens in new window), ZAP exposes three environment variables for header-based authentication injection - useful for pre-obtained bearer tokens (OAuth client-credentials flow, API keys, CI-issued JWTs):

VariablePurpose
ZAP_AUTH_HEADER_VALUEThe token value (Bearer eyJ...)
ZAP_AUTH_HEADERHeader name (defaults to Authorization if unset)
ZAP_AUTH_HEADER_SITERestrict injection to this domain only

Set these in the CI environment before running the scan:

export ZAP_AUTH_HEADER_VALUE="Bearer $(./scripts/get-ci-token.sh)"
export ZAP_AUTH_HEADER_SITE="app.example.com"

docker run --rm \
  -e ZAP_AUTH_HEADER_VALUE \
  -e ZAP_AUTH_HEADER_SITE \
  -v $(pwd):/zap/wrk/:rw \
  ghcr.io/zaproxy/zaproxy:stable \
  zap-full-scan.py -t https://app.example.com -n /zap/wrk/context.xml -J report.json

For OAuth flows requiring a full authorization-code exchange, use Script-Based auth (references/script-based-auth.md (opens in new window)) to run the exchange inside ZAP and let ZAP manage token refresh during the scan. Environment-variable injection is the right path for client-credentials and static-API-key auth.

ZAP Script-Based Authentication

View source (opens in new window)

ZAP Script-Based Authentication

Per zap-methods (opens in new window), Script-Based auth handles flows that Form-Based and JSON-Based cannot: OTP-augmented logins, multi-step forms, OAuth authorization-code flows with PKCE, or apps that rotate CSRF seeds on every page load.

Prerequisites:

  1. Install the Script Console add-on from the ZAP Marketplace.
  2. In Tools > Scripts, create a new Authentication script (type: Authentication). ZAP ships example scripts at scripts/authentication/ inside the ZAP installation directory.
  3. The script receives helper, paramsValues, and credentials; it must call helper.prepareMessage() to build a login request and return the response.

Minimal skeleton (Groovy):

def authenticate(helper, paramsValues, credentials) {
    def loginUrl = paramsValues.get("Login URL")
    def msg = helper.prepareMessage()
    msg.setRequestHeader("POST " + loginUrl + " HTTP/1.1\r\n" +
        "Host: app.example.com\r\n" +
        "Content-Type: application/json\r\n")
    def body = '{"user":"' + credentials.getParam("Username") + '",' +
               '"pass":"' + credentials.getParam("Password") + '"}'
    msg.setRequestBody(body)
    helper.sendAndReceive(msg)
    return msg
}

Select the script in Session Properties > Context > Authentication > Script-Based Authentication, then set any script parameters.

For OAuth authorization-code flows: the script fetches the /authorize redirect, extracts the code, POSTs to /token, and stores the resulting access_token in a ZAP environment variable for header injection (see references/oauth-bearer-injection.md (opens in new window)).

ZAP Authentication Verification Strategy

View source (opens in new window)

ZAP Authentication Verification Strategy

Per zap-verify (opens in new window), ZAP uses an Authentication Verification Strategy to know whether a request is executing as an authenticated user. Configure in Session Properties > Context > Authentication > Verification:

Logged-In Indicator: a regex present in responses when the user is authenticated. Examples:

  • \QWelcome, \E (welcome banner with the username)
  • \Qhref="/logout"\E (logout link in nav)
  • \Q"role":"user"\E (JSON response field)

Logged-Out Indicator: a regex present in responses when the session has expired. Examples:

  • \QPlease log in\E
  • \Qlocation: /login\E (redirect header)
  • HTTP/1\.1 401

Per zap-verify (opens in new window), four strategies are available:

StrategyUse when
Check Every ResponseTraditional HTML apps (indicator in page body)
Check Every RequestClient-side sessions (JWT in Authorization header)
Check Every Request or ResponseMixed; SPA + API combo
Poll the Specified URLDedicated /api/me or /session/check endpoint

Calibration steps:

  1. Browse the app manually through ZAP proxy while logged in.
  2. Right-click a response in the History tab that contains the logged-in text. Choose Flag as Context > <context-name> Logged in indicator. ZAP extracts the regex automatically.
  3. Browse to a page after logging out. Right-click that response. Choose Flag as Context > <context-name> Logged out indicator.
  4. Confirm both indicators in Session Properties > Context > Authentication.

Related skills

burp-headless

Configures and runs headless Burp Suite Professional / Enterprise vulnerability scans (a "Burp scan"): Pro drives scans via its local REST API, Enterprise runs CI-driven scans at scale via its server API; supports BApp Store extensions (BCheck, custom scanners) and authenticated targets via session-handling rules; exports issues as HTML / XML / CSV / JSON or SARIF. Use when the team has a Burp Suite license and wants to run a vulnerability scan with Burp - paid-tier dynamic application security testing (DAST) layered on top of OWASP ZAP.

dast-scan-cadence-author

Designs an end-to-end DAST cadence for teams adopting dynamic scanning: ZAP passive baseline (PR-blocking) then ZAP full active scan (nightly on staging) then optional Burp Pro deep scan (per-release). Handles the baseline-finding ratchet for legacy apps so pre-existing findings do not immediately block PRs, plus per-tool per-run deduplication and CI workflow YAML. Use when the team is setting up DAST from scratch or restructuring scan cadence, not when tools are already running and you need to merge their output (cross-tool aggregation of existing independent runs is a separate concern).

nightvision-dast

Configures and runs NightVision white-box-assisted DAST: analyzes source code before attacking, traces every finding to its origin line, and drives coverage from OpenAPI / Postman / GraphQL specs rather than crawling. Supports Header, Cookie, TOTP, and recorded Interactive Login auth; exports findings as SARIF for GitHub Code Scanning, plus JSON, CSV, or PDF. Per-finding suppression via Alert Rules; CLI integration via the `nightvision` command. Use when source-traceable findings and spec-driven request coverage matter, not just authenticated black-box scanning (see zap-authenticated-scans for that).

nuclei-dast

Installs and runs ProjectDiscovery Nuclei template-based HTTP scanning: selects templates via `-t {path}` and `-tags`/`-severity` filters, controls request rate with `-rl`, emits JSONL output via `-j` for cross-tool finding aggregation, authors custom YAML matchers for app-specific checks, and gates CI on severity thresholds. Use when the team runs Nuclei alongside ZAP for template-driven DAST coverage, needs fuzzing-style probes beyond ZAP passive scan, or wants to operationalize community CVE templates in a pipeline.

zap-baseline

Configures and runs OWASP ZAP baseline scanning: `zap-baseline.py` Docker-packaged spider + passive scan suitable for CI gating; supports `-t target_url` + `-r html_report` + `-c config_file` rule customization (INFO/IGNORE/FAIL warnings) and Ajax spider via `-j` for JS-heavy SPAs. Passive-only; for active injection probes use `zap-full-scan.py` via zap-authenticated-scans. Accepts `-n context_file` for pre-configured auth contexts (see zap-authenticated-scans for setting up auth from scratch). Use when the user runs OWASP ZAP for pre-prod web app DAST.