Skip to main content

Authentication scripts

This document explains authentication scripts - custom JavaScript scripts that manage login sessions and token-based authentication during scans. It covers how to write, upload, and debug authentication scripts with practical examples for Bearer token and JWT authentication.

tip

Contact the Support team to enable the pre-request scripts in Invicti Platform on-demand.

Step 1: understand authentication scripts

Authentication scripts are custom JS scripts, a subset of pre-request scripts, that manage how a system logs in and maintains a valid session for authentication, for example where an authentication code such as a token needs to be requested at the very beginning of the scan, and possibly re-requested periodically during the scan. The token needs to be used in all requests to avoid an authentication error and lack of scan coverage.

How authentication scripts work

Authentication scripts provide 3 main entry points, each defining an expected logic implemented within them.

  • acquireSession()

    • Get a new token depending on the type specified by the user and add it to the session. For example, by adding an Authorization header with the token received as value.
    • This operation is atomic and blocking; when invoked, all outgoing requests requiring a session are queued and wait for this process to complete before proceeding.
  • authenticateRequest()

    • Add any authentication data such as tokens to a request. This could also include signatures and any other mechanism related to authorization, authentication, or verification.
  • isReAuthenticationRequired()

    • Determine whether a token is still valid. In token authentication, this could be:
      • time-dependent where a token needs to be re-obtained every x minutes
      • depending on response received for every request within the session for example by checking response status code, or possibly even body contents.

Structure of authentication scripts

Directive

Authentication scripts need only one directive at the very top which is always fixed. This serves to identify the JS file as an authscript.

/// script_type: authscript

Script arguments per interface function

Any authentication script is defined by three primary entry points:

function acquireSession(session)
function authenticateRequest(http)
function isReAuthenticationRequired(http)

The session and http arguments are native objects provided by the engine at runtime, representing the active session state and the current HTTP task. The specific properties and methods available within these objects are defined by the Session and Job interfaces located in the native.d.ts definition file.

External modules

Scripts can load and call external modules if they meet the following criteria:

  • The modules use vanilla JavaScript only.
  • The script loads them using the API call ax.loadModule(<module name>).
  • You also upload the module and its name matches <module name>.

Step 2: write your script

To create an authentication script:

  1. Add the directive at the top to identify the script as an authentication script.
  2. Use the provided example templates and write your logic using vanilla JavaScript and call the Invicti Platform's native API through defined interfaces (native.d.ts) as needed.
  3. Call the external modules via ax.loadModule. These modules must be written in vanilla JavaScript, imported into the target settings and the imported filename must match exactly the argument to ax.loadModule.
  4. Save the script as a JS file.
warning

Each target supports only one script: pre-request or authentication.

Authentication scripts examples

The following are examples of auth scripts available for use in scans.

Basic skeleton template

This example provides just layout structure and requires logic edits in the script to authenticate calls. Refer to the specific script examples in the following sections to authenticate with a Bearer token or JWT.

/// script_type: authscript

const AuthScriptSettings = {
reauthenticationTimeInMs: 0
};

let tokenObtainedTime = 0;

function acquireSession(session)
{
//request token
let newjob = ax.http.job();
//add url, headers , method and any payload to the job
ax.http.execute(newjob).sync();
if (newjob.error)
{
ax.loge("error returned for acquireSession request from auth script");
return false;
}

//parse response for possible session information
const response = newjob.response;

let success = true; //replace this with result of response parsing

if (success)
{
tokenObtainedTime = new Date().getTime();
return true;
}
return false;
}

function authenticateRequest(http)
{
// Add token to request (for example, set Authorization header with Bearer token). Return true if authentication is applied successfully.
return false;
}

function isReAuthenticationRequired(http)
{
//reauthenticationTimeInMs = 0 means that no time-base re-authentication is needed
const currentTime = new Date().getTime();
if (AuthScriptSettings.reauthenticationTimeInMs && (currentTime - tokenObtainedTime >= AuthScriptSettings.reauthenticationTimeInMs))
{
return true;
}

//if no time-based expiry or if still within token life-time
// match response code and body content

const responseStatus = http.response.status;
const responseBody = http.response.body;

// this entry point is called after http.response is available, so it can be evaluated
// for possible hints about whether re-authentication is needed

return false;
}
Authentication by Bearer token
/// script_type: authscript

const AuthScriptSettings = {
authenticationType: `Bearer`,

tokenRequest: {
url: `$targetUrl/getToken`,
method: "GET",
headers: {
'TestHeader': 'TestValue',
'TestHeader2': 'TestValue2'
},
body: ''
},
expectedResponseContentType: `application/json`,
tokenPathInResponse: `/token`,
tokenLifetimeInMs: 60000,
tokenResponseTriggers: [
{
statusCode: "401",
bodyContentRegex: "\\btoken\\b"
}
]
};

let tokenValue = "";
let tokenObtainedTime = 0;

function acquireSession(session)
{
//request token
let tokenJob = ax.http.job();
tokenJob.request.method = AuthScriptSettings.tokenRequest.method;
tokenJob.setUrl(ax.url.parse(AuthScriptSettings.tokenRequest.url));
for (const [key, value] of Object.entries(AuthScriptSettings.tokenRequest.headers)) {
tokenJob.request.setHeader(key, value);
}
tokenJob.request.body = AuthScriptSettings.tokenRequest.body;

ax.http.execute(tokenJob).sync();
if (tokenJob.error)
{
ax.loge("error returned for acquireSession request from auth script");
return false;
}

//parse token from response
const response = tokenJob.response;

if (response.headers.get('Content-Type').includes('/json'))
{
tokenValue = ax.util.extractValueFromJSON(response.body, AuthScriptSettings.tokenPathInResponse);
}
else
{
tokenValue = response.body;
}

tokenObtainedTime = new Date().getTime();
ax.logi("Token acquired: " + tokenValue);
return true;

}

function authenticateRequest(http)
{
if (AuthScriptSettings.authenticationType === `Bearer`)
{
http.request.setHeader(`Authorization`, `Bearer ` + tokenValue);
return true;
}
ax.loge(`Unsupported authentication type : ` + AuthScriptSettings.authenticationType);
return false;
}

function isReAuthenticationRequired(http)
{
//tokenLifetimeInMs = 0 means that token expiry is not time-based
const currentTime = new Date().getTime();
if (AuthScriptSettings.tokenLifetimeInMs && (currentTime - tokenObtainedTime >= AuthScriptSettings.tokenLifetimeInMs))
{
ax.logi("authentication token needs to be reacquired [token lifetime exceeded]");
return true;
}

//if no time-based expiry or if still within token life-time
// match response code and body content
const responseStatus = http.response.status;
const responseBody = http.response.body;

for (const responseTrigger of AuthScriptSettings.tokenResponseTriggers) {
if (responseTrigger.statusCode === responseStatus.toString()) {
const regex = new RegExp(responseTrigger.bodyContentRegex);
if (regex.test(responseBody)) {
ax.logi("authentication token needs to be reacquired based on response");
return true;
}
}
}

return false;
}
  • Line 3: AuthScriptSettings class defines all the data needed by the script.

  • Line 7: replace $targetUrl/getToken with a valid URL.

  • Line 10: replace the placeholder HTTP header and value ('TestHeader': 'TestValue') with your own.

  • Line 29: acquireSession() sends a request to retrieve a new token, waits for the response and attempts to extract the token. If successful, it saves the token to be used to authenticate requests.

  • Line 65: authenticateRequest() adds and Authorization Bearer token to the request and includes the current token value.

  • Line 76: isReauthenticationRequired() checks whether the token's lifetime has ended, in which case it returns true meaning that the engine should call acquireSession() again before continuing the authenticated scan.

    • Even if lifetime hasn’t expired or wasn’t specified, the response of the current job is evaluated for potential errors related to expired token. In which case it also returns true.
    • If none are found, then it returns false meaning that the engine can continue the authenticated scan without further action related to session.
Authentication by JWT

This example demonstrates how to use a template to customize an authentication script for a specific target, such as one requiring JWT.

/// script_type: authscript

const AuthScriptSettings = {
getTokenRequestUrl: `$targetUrl/getJWT`,
headers: {
'Content-Type': 'application/json'
},
clientId: `your-client-id`,
clientSecret: `your-client-secret`,
grantType: `client_credentials`,
expectedResponseContentType: `application/json`,
tokenPathInResponse: `/token`,
tokenLifetimeInMs: 60000,
authenticationType: `JWT`,
responses: [
{
statusCode: "401",
bodyContentRegex: "\\bexpired\\b"
}
]
};

let tokenValue = "";
let tokenObtainedTime = 0;

function acquireSession(session) {
// Request token
let newjob = ax.http.job();
newjob.setUrl(ax.url.parse(AuthScriptSettings.getTokenRequestUrl));
newjob.request.method = "POST";
for (const [key, value] of Object.entries(AuthScriptSettings.headers)) {
newjob.request.setHeader(key, value);
}
newjob.request.body = JSON.stringify({
client_id: AuthScriptSettings.clientId,
client_secret: AuthScriptSettings.clientSecret,
grant_type: AuthScriptSettings.grantType
});
ax.http.execute(newjob).sync();
if (newjob.error) {
ax.loge("Error returned for acquireSession request from auth script");
return false;
}

// Parse token from response
const response = newjob.response;

if (AuthScriptSettings.expectedResponseContentType.endsWith("/json")) {
tokenValue = ax.util.extractValueFromJSON(response.body, AuthScriptSettings.tokenPathInResponse);
} else {
ax.loge("Unsupported expected response content type [?]", AuthScriptSettings.expectedResponseContentType);
return false;
}

tokenObtainedTime = new Date().getTime();
ax.logi("JWT Token acquired: " + tokenValue);
return true;
}

function authenticateRequest(http) {
if (AuthScriptSettings.authenticationType === `JWT`) {
http.request.setHeader(`Authorization`, `Bearer ` + tokenValue);
return true;
}
ax.loge(`Unsupported authentication type: ` + AuthScriptSettings.authenticationType);
return false;
}

function isReAuthenticationRequired(http) {
// Token lifetime check
const currentTime = new Date().getTime();
if (AuthScriptSettings.tokenLifetimeInMs && (currentTime - tokenObtainedTime >= AuthScriptSettings.tokenLifetimeInMs)) {
ax.logi("JWT token needs to be reacquired [token lifetime exceeded]");
return true;
}

// Match response code and body content
const responseStatus = http.response.status;
const responseBody = http.response.body;

for (const responseTrigger of AuthScriptSettings.responses) {
if (responseTrigger.statusCode === responseStatus.toString()) {
const regex = new RegExp(responseTrigger.bodyContentRegex);
if (regex.test(responseBody)) {
ax.logi("JWT token needs to be reacquired based on response");
return true;
}
}
}

return false;
}
  • Line 26: The acquireSession() function contains additional implementation related to forming a standard request for a JWT , but the other interface functions are very similar to the Authentication Bearer template.

Step 3: upload the file into UI

Follow the steps to upload the authentication script file for a specific target:

  1. Select Inventory > Targets from the left-side menu.
  2. Use the three-dot menu (⋮) > Edit target for the specific target you want to update.
  3. In the Scan configuration menu select General.
  4. Upload the JS file with the script by clicking Upload file.
Upload file button in the scan configuration section.
  1. In Type drop-down select Pre-request Script (.js) option then click Choose file to select the script file.
Type dropdown with pre-request script option selected.
  1. Click Upload file to save it.
  2. Optionally, if you're using external modules, repeat the steps 4 - 6 to upload the module.js file.
  3. Save the settings to target by clicking Save target configuration or Save and scan.

Step 4: test and debug authentication scripts

This section covers script validation, logging techniques for debugging, analyzing scan logs and error notifications, and debugging strategies.

Test your script before uploading

Before uploading your authentication script to Invicti Platform, validate it locally to catch errors early:

  1. Verify the mandatory directive: ensure the script begins with the exact line /// script_type: authscript. This directive is mandatory for the engine to recognize and execute the file as an authentication script.

  2. Validate syntax and formatting: run your code through a JavaScript linter or an IDE to catch basic errors, missing semicolons, or typos that would cause the script to fail immediately upon upload.

  3. Test logical functions locally: test your acquireSession and isReAuthenticationRequired logic in a local Node.js or browser environment to ensure your math and conditional statements behave as expected.

  4. Check for secure transmission: use a network proxy or console log to confirm that tokens are sent exclusively via HTTPS and that the authenticateRequest function correctly attaches the Authorization header.

  5. Simulate session expiry and recovery: manually trigger a 401 Unauthorized response or a timeout condition to verify that the script detects the failure and recovers the session without manual intervention.

Use logging for debugging

Add logging statements to your authentication scripts to track execution flow and state of your script by inserting them at key entry points:

  • Inside acquireSession: log when a login attempt starts and whether it succeeds. For example, ax.logi("Attempting to acquire new session...");.

  • Inside authenticateRequest: log that a token is being attached to an outgoing request to verify the function is actually running.

  • Inside isReAuthenticationRequired: log the reason why a re-auth was triggered. For example, ax.logi("Token expired based on time: " + currentTime);.

tip

Review the Bearer and JWT authentication script examples to learn how to use logging for debugging.

Debug failed scripts in Invicti Platform

When an auth script fails during a scan, Invicti Platform provides error information in multiple locations.

Find error notifications

Failed pre-request scripts trigger notifications in the Scan details page:

In the Scan Summary tab:

Scan summary tab showing auth script failure notification.

In the Activity tab:

The Activity tab shows a scanner event with details about the authentication script failure.

Activity tab displaying scanner event for auth script failure.

Analyze scan logs for detailed errors

note

For information on downloading scan logs, refer to the Download scan logs document.

Scan logs contain the complete error messages with stack traces. Look for entries starting with ERROR and including auth scripts or your script filename.

Example error in scan log:

ERROR	session	42188	Error: HttpJob: unable to set url: $placeholder/getToken in auth-script-bearer.js:34
tokenJob.setUrl(ax.url.parse(AuthScriptSettings.tokenRequest.url));
Error: HttpJob: unable to set url: $placeholder/getToken
at acquireSession (auth-script-bearer.js:34:14)

This error shows:

  • Object: HTTPJob - object handling the request
  • Problem: unable to set url - provided URL wasn't a valid web address
  • Location: auth-script-bearer.js:34 - line 34 in your script file
  • Fix: Change $placeholder to a valid URL
tip

If you added logging statements with ax.logi() or ax.loge(), those messages also appear in the scan logs with your custom text.

Debugging strategies

Start simple and build up:

  1. Begin with a minimal script that only logs token acquisition
  2. Verify acquireSession() executes successfully
  3. Add authentication and re-authentication logic incrementally, testing after each addition

Trace authentication state:

  1. Use ax.logi() to log state transitions (Logged out → Logging in → Logged in → Expired)
  2. Log when isReAuthenticationRequired() triggers a new token request
  3. Verify token values are correctly extracted and applied

Inspect network requests:

  1. Use a proxy tool like Fiddler to examine raw HTTP requests
  2. Check for formatting issues in the Authorization header (double spaces, typos)
  3. Look for hidden characters like %0A or %0D that could invalidate tokens

Test failure handling:

  1. Temporarily change the token endpoint to an invalid URL to force an error
  2. Verify that isReAuthenticationRequired() correctly detects expired tokens
  3. Confirm that acquireSession() is called when needed

Need help?

Invicti Support team is ready to provide you with technical help. Go to Help Center

Was this page useful?