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-scanszap-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:
How to use
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:
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 type | Method to use |
|---|---|
| HTML form POST with username + password fields | Form-Based |
JSON POST {"username":"...","password":"..."} | JSON-Based |
| HTTP Basic / Digest / NTLM challenge | HTTP/NTLM |
| Custom flow (OTP, magic link, multi-step) | Script-Based |
| Modern browser-rendered SSO / OAuth redirect | Browser-Based (auth-helper addon) |
Step 3 - Configure Form-Based Authentication
Per zap-methods (opens in new window), Form-Based auth requires:
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:
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 type | Method |
|---|---|
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 rotation | Script-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_PASSStep 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.jsonPer 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:
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.
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-pattern | Why it fails | Fix |
|---|---|---|
Skip context creation, use -u user:pass flag | No re-auth; spider logs out mid-scan | Context + auth method (Steps 1-3) |
| Hardcode credentials in context.xml | Secrets leak in version control | -config injection or env vars (Step 10) |
| No logged-out indicator | ZAP reports false coverage on expired sessions | Calibrate both indicators (Step 9) |
| Form-based auth on a JSON-API login | ZAP sends form-encoded body; app rejects it | JSON-based auth (Step 4) |
Exclude /login from context scope | Auth POST never proxied; ZAP can't authenticate | Include login URL; exclude only /logout (Step 1) |
| Browser-based auth without auth-helper addon | method: browser is not a built-in; scan fails | Install Authentication Helper from Marketplace (Step 6) |
| Set verification strategy but no indicators | Strategy is inactive; ZAP never detects re-auth need | Supply at least one logged-in regex (Step 9) |
Limitations
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):
| Variable | Purpose |
|---|---|
ZAP_AUTH_HEADER_VALUE | The token value (Bearer eyJ...) |
ZAP_AUTH_HEADER | Header name (defaults to Authorization if unset) |
ZAP_AUTH_HEADER_SITE | Restrict 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.jsonFor 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:
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:
Logged-Out Indicator: a regex present in responses when the session has expired. Examples:
Per zap-verify (opens in new window), four strategies are available:
| Strategy | Use when |
|---|---|
| Check Every Response | Traditional HTML apps (indicator in page body) |
| Check Every Request | Client-side sessions (JWT in Authorization header) |
| Check Every Request or Response | Mixed; SPA + API combo |
| Poll the Specified URL | Dedicated /api/me or /session/check endpoint |
Calibration steps:
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.