ZAP Authentication — Lessons Learned
This section summarizes key best practices, common pitfalls, and debugging tips for authenticated scans in ZAP automation. Following these guidelines ensures more reliable scan execution and accurate authentication verification.
1. Host/URL Order
ZAP uses the first host in the hosts list as the primary domain for authentication verification:
hosts:
- https://login.example.com # loginPageUrl domain first ✅
- https://api.example.com # backend/API domain second
Rules:
- Browser auth →
loginPageUrldomain must be first - JSON/API auth →
loginRequestUrldomain must be first
Incorrect order symptoms:
- "Username field not identified" → loginPageUrl not first
- Auth report shows wrong domain → login API domain not first
2. Authentication Method Selection
| Method | Use Case | Limitations |
|---|---|---|
json | SPA/API apps with REST login returning token | Cannot handle client-side encryption, custom headers, complex redirects |
form | Server-rendered apps with HTML forms (PHP, Django, Rails) | Session cookie based only, no token |
browser | Complex apps with RSA-encrypted passwords, SSO, OAuth, MFA | Handles encryption, redirects, custom headers automatically |
3. loginRequestBody Format
Always use a single-line JSON string. Multi-line bodies may get corrupted in afEnv.
# Correct
loginRequestBody: '{"loginId":"{%username%}","password":"{%password%}"}'
# Incorrect
loginRequestBody: |-
{
"loginId": "{%username%}",
"password": "{%password%}"
}
4. Custom Login Headers
loginRequestHeaders is ignored by ZAP. Use a Graal.js httpsender script to inject headers on login and subsequent requests:
// add-headers.js
function sendingRequest(msg, initiator, helper) {
msg.getRequestHeader().setHeader("customer-name", "ACME");
msg.getRequestHeader().setHeader("tenant-id", "1");
msg.getRequestHeader().setHeader("login-id", "user123");
}
function responseReceived(msg, initiator, helper) {}
jobs:
- name: injectHeaders
type: script
parameters:
action: add
type: httpsender
engine: Graal.js
name: AddHeaders
file: /zap/wrk/add-headers.js
5. Dynamic Token Extraction
ZAP only extracts JWT/Bearer tokens dynamically. Short strings or numeric values must be hardcoded in sessionManagement.
sessionManagement:
method: headers
parameters:
Authorization: Bearer {%json:results.accessToken%} # dynamic
tenant-id: "1" # static
login-id: "user123" # static
6. Verification Method
| Method | Use Case | Notes |
|---|---|---|
poll | Backend API endpoint | Preferred for SPA, JSON, and browser auth |
response | Specific endpoint response | Optional for JSON auth only |
autodetect | Automatic traffic analysis | Unreliable; avoid for SPA and API auth |
Rules for pollUrl:
- Must be a GET endpoint
- Should point to backend API, not SPA frontend route
- Test with curl before using in ZAP
7. loggedOutRegex
- Match unique error text in response body for failed authentication
- Avoid matching HTTP status codes directly
loggedOutRegex: 'Invalid Credentials'
8. SPA Applications
- Frontend SPA URLs (
/#/dashboard) return empty HTML; never use for pollUrl - Backend API URLs return actual data → use for pollUrl and scan targets
- Enterprise SPAs may require OpenAPI spec or HAR import for full coverage
9. Browser vs JSON Auth for RSA Encryption
- Browser auth runs real JS encryption for each login
- JSON auth only works if the encrypted string is static/reusable
- Browser auth recommended for apps with client-side crypto
10. pollFrequency
- Short scans → 10 seconds for quick re-auth and session verification
- Long scans → increase as needed (30+ mins)
11. Memory and Container Sizing
| Scan Type | Container | JVM | CPU |
|---|---|---|---|
| Basic scan | 4G | 2G | 2 |
| Advanced scan | 8G | 4G | 4 |
| Browser scan | 12G | 6G | 4 |
- Chrome runs outside JVM heap → needs ~4G additional memory
- JVM heap should be ≤50–60% of container memory
12. Debugging Checklist
-
Check
afEnvinauth-report.jsonfor parsed body and parameters -
Review
stats.auth.detect.sessionfor token extraction -
Test
pollUrlmanually with curl and required headers -
Inspect login requests in browser DevTools → headers and payload
-
Review
summaryItemsfailure reasons:- "No successful logins" → form not found or submit failed
- "No indication of login" → regex mismatch
- "Username not identified" → loginPageUrl domain not first