Akadenia SecurityAkadenia Security

Scanning signed in

Give the web scanners a test account so they check what's behind sign-in, including APIs that use expiring tokens.

Most of an app sits behind its sign-in page, out of reach of a scanner that visits as a stranger. Give a target a test account and the ZAP and nuclei scans sign in with it, then test what a signed-in user sees.

Scanning signed in is part of paid plans. Owners and admins set it up on a target's Signed-in scans page.

Use a test account

Use an account made for testing, never a real person's. Scans use it the way a person would: they open its pages and follow its links. Give it the access you want tested, and nothing it could break.

How the app signs in

  • Header or cookie: a header sent with every request, such as Authorization: Bearer … with a long-lived API key, or Cookie: session=….
  • HTTP basic: a user name and password, sent as an Authorization: Basic header.
  • Login form: a user name and password posted to the app's login form. The scan keeps the session cookies the form sets. Give the form's URL and, if they aren't username and password, its field names.
  • Token from a login call or OAuth: for APIs whose tokens expire. Each scan fetches a fresh token just before it starts, so an expiring token works as long as it lasts longer than a scan. See Tokens that expire.

Tokens that expire

A header login sends the same value every time, which works for API keys but not for an OAuth access token or a JWT from a login call: once it expires, every scan fails its sign-in check. A token login fetches a new one for each scan instead, in one of two ways.

  • OAuth 2 client credentials: the token URL (such as https://your-tenant.auth0.com/oauth/token), the client ID and secret, and, if your provider asks for them, a scope and an audience. The client ID and secret go in an HTTP basic header by default; choose the request body if your provider wants them there. The token is read from the answer's access_token.
  • A login request that answers with a token: the URL and method (POST or PUT), the JSON body to send (with the test account's user name and password), and where the token is in the JSON answer, as a dotted path: token, data.accessToken, or tokens.0.value for the first item of a list.

Either way, say how requests carry the token: the header (Authorization unless you change it) and its value, with {token} where the token goes (Bearer {token} unless you change it; {token} alone for a header like X-Api-Token).

The token URL can be any https URL: your own, or your identity provider's (Auth0, Okta and the like). Its address is checked to be public before each scan, and redirects aren't followed.

A token should last longer than a scan takes (up to an hour). One that runs out partway is caught by the check after the scan, which then fails rather than passing as clean; one the answer says lasts under 5 minutes (expires_in) fails the scan before it starts.

For an API, also give ZAP its OpenAPI definition: its crawl finds little on an API otherwise.

Checking the login works

Before any scanner runs, each scan signs in and loads a page to check it worked, and loads it again when the scanners finish. If the login fails, or the session ended during the scan, the scan fails and says why ("the token URL answered 401", "signed out during the scan"), rather than passing as clean.

  • Page the sign-in check loads: the home page unless you give a path. An API whose root answers 401 or 403 even to a signed-in user needs one that answers 200 when signed in, such as /me or /health.
  • Text shown only when signed in, or only when signed out: plain text the check looks for on that page, such as "Sign out" or "Sign in". A login form needs at least one of them, and a token login needs one of them or a page to check: a public page answers 200 to any token. For a header or HTTP basic login, a 401 or 403 answer is enough to tell.

Staying safe

  • Never requested while signed in: paths or words that would sign the test account out or change it. Any URL containing one of them is skipped. The defaults cover logging out, deleting and deactivating, unsubscribing and password changes; add your own.
  • Most requests a second: a limit for the signed-in scan, if your app needs one.
  • ZAP's scan is passive: it only makes the requests a crawler would, never attack traffic.

Your secrets

The header value, password, client secret or login request body is encrypted as soon as you save it, for this target only, and never shown again: you can replace it or remove it, not read it. Only a scan decrypts it, for as long as that scan runs.

What a scan keeps (its reports and the evidence for its findings) never holds the session: the header value, the session cookies, a fetched token, the passwords and the secret values of a login request's body (such as its password, client secret or PIN) are replaced, as written in JSON and URLs too, with [redacted] before anything is stored.