Troubleshooting Authenticated Context Configuration in ZAP

67 views
Skip to first unread message

wsdmer

unread,
Oct 1, 2025, 11:13:40 AM (6 days ago) Oct 1
to ZAP User Group
Hi everyone,

I'm currently working on a DAST project using ZAP to scan our application, which relies on Keycloak and the OpenID Connect protocol.

To perform authenticated scans, I followed the recommended approach and used the Authentication Tester tool to configure a context. It helped me set up the poll request and the Logged In / Logged Out patterns.

To properly handle the authentication flow, I wrote a custom authentication script, inspired by the juice shop  authentication scrit with selenium. Here's what it looks like:

from org.openqa.selenium import By
from org.zaproxy.zap.extension.script import ScriptVars
from java.lang import Thread
from org.apache.commons.httpclient import URI
from org.zaproxy.zap.extension.selenium import ExtensionSelenium
import java.lang.String, jarray
from org.parosproxy.paros.network import HttpRequestHeader


def authenticate(helper, paramsValues, credentials):
    print("[ZAP Jython Auth Script] Authenticating via Jython script...")

    target = ScriptVars.getGlobalVar("target")
    if target is None:
        print("[ZAP Jython Auth Script] targetURL parameter is not defined!")
        return

    username = credentials.getParam("username")
    password = credentials.getParam("password")
    extSel = control.getExtensionLoader().getExtension(ExtensionSelenium)
    wd = extSel.getWebDriverProxyingViaZAP(5, "firefox")

    print("[ZAP Jython Auth Script] Navigating to: " + target)
    wd.get(target)
    Thread.sleep(1000)

    print("[ZAP Jython Auth Script] Entering credentials...")
    wd.findElement(By.id("username")).sendKeys(username)
    wd.findElement(By.id("password")).sendKeys(password)
    wd.findElement(By.id("kc-login")).click()

    Thread.sleep(5000)
    wd.quit()
    Thread.sleep(100)

    print("[ZAP Jython Auth Script] Authentication finished!")

    # Préparation d'un message vide (nécessaire pour compatibilité)
    msg = helper.prepareMessage()
    requestURI= URI("{}/api/v1/version".format(target))
    requestHeader = HttpRequestHeader(HttpRequestHeader.GET, requestURI, HttpRequestHeader.HTTP11)
    msg.setRequestHeader(requestHeader)
    helper.sendAndReceive(msg)
   
    return msg


def getRequiredParamsNames():
    return jarray.array([], java.lang.String)

def getOptionalParamsNames():
    return jarray.array([], java.lang.String)

def getCredentialsParamsNames():
    return jarray.array(["username", "password"], java.lang.String)


To test the context, I created an automation plan :


env:
  contexts:
  - name: Application
    urls:
    - example.com
    includePaths:
    - example.com.*
    authentication:
      method: script
      parameters:
        script: ~/.ZAP/scripts/scripts/authentication/CustomAuthentication.py
        scriptEngine: jython
      verification:
        method: poll
        loggedInRegex: \Q 200 OK\E
        loggedOutRegex: \Q 401 Unauthorized\E
        pollFrequency: 60
        pollUnits: requests
        pollUrl: example.com/api/v1/version
        pollPostData: ""
    sessionManagement:
      method: headers
      parameters:
        Authorization: "Bearer {%json:access_token%}"
    technology: {}
    structure: {}
    users:
    - name: us...@example.com
      credentials:
        password: ****************
        username: us...@example.com
  parameters: {}
jobs:
- type: requestor
  parameters:
    user: us...@example.com
  requests:
    method: GET
    responseCode: 200
  alwaysRun: true



Here's the strange behavior I'm encountering:

  • When there’s an error at the end of the authenticate() function in the authentication script like a mistake in variable name example : requestURIIIIIIII, everything works fine — the {%json:access_token%} placeholder is correctly interpreted.
with_error_tab_history.png
  • But when the script runs without errors, the token is not interpreted correctly.

without_error_history_tab.png

I've compared th HTTP request flow, but there is no significant difference.

I'm pretty confused by this result — can anyone help me?

How {%json:path_to_data} work ? 

PS: I have another question about ZAP.
Can someone tell me when the authentication script is triggered during an active scan?
Is it based on the Logged Out pattern or something else?

Simon Bennetts

unread,
Oct 3, 2025, 1:01:23 PM (4 days ago) Oct 3
to ZAP User Group
Do you _really_ need to create your own script?
That should only be a last resort, as it can be painful to debug (as you have found) ;)


Browser Based Auth and Client Script Auth are the recommended options.

If they are not working for you then can you explain why?

We want to keep improving them so that they handle the vast majority of cases, but if they really wont work for you now then we can look into your scripting problems.

Cheers,

Simon

wsdmer

unread,
Oct 6, 2025, 4:07:23 AM (yesterday) Oct 6
to ZAP User Group
Hi Simon,

Thanks for your reply!

Yes, and  I’ve already tried Browser-Based Authentication. While it initially appears to work, I’m encountering some strange behavior. Everything looks fine at first, but when I inspect the session management configuration in my context, the expected header value is missing

authentifcation_tester.png context_session_management.png

For Client Script Authentication, I Don't think is a good approach in ourcase.

Another issue I’m facing, and one of the reasons I started working on a custom script, is that when the user logs out or the browser used by ZAP is closed at the end of authentication, the token gets revoked.

Currently, I’m experimenting with a hybrid approach using a Selenium script to perform the initial login and keep the browser open in the backgroun,combining it with the OfflineTokenRefresh.js  script to refresh the token for subsequent authentications.

I’m not entirely sure this is the best solution, but for now, it’s the only one I have.

Reply all
Reply to author
Forward
0 new messages