zaproxy/zaproxy · error · AuthenticationException
Unexpected state: {state}
Error message
Unexpected state: {state} What it means
ZapNTLMScheme.authenticate() throws this AuthenticationException when the NTLM state machine is in a state that cannot produce an NTLM message. authenticate() only handles CHALLENGE_RECEIVED (generate Type 1 msg) and MSG_TYPE2_RECEVIED (generate Type 3 msg); reaching it in UNINITIATED, MSG_TYPE1_GENERATED, MSG_TYPE2_GENERATED, or MSG_TYPE3_GENERATED state falls into the else branch and throws. This happens when the HTTP client calls authenticate() out of order relative to processChallenge(), typically because authenticate() was invoked before any server challenge was processed or after authentication already completed.
Source
Thrown at zap/src/main/java/org/zaproxy/zap/network/ZapNTLMScheme.java:167
}
String response = null;
if (this.state == State.FAILED) {
throw new AuthenticationException("NTLM authentication failed");
} else if (this.state == State.CHALLENGE_RECEIVED) {
response = this.engine.generateType1Msg(
ntcredentials.getDomain(),
ntcredentials.getHost());
this.state = State.MSG_TYPE1_GENERATED;
} else if (this.state == State.MSG_TYPE2_RECEVIED) {
response = this.engine.generateType3Msg(
ntcredentials.getUserName(),
ntcredentials.getPassword(),
ntcredentials.getDomain(),
ntcredentials.getHost(),
this.challenge);
this.state = State.MSG_TYPE3_GENERATED;
} else {
throw new AuthenticationException("Unexpected state: " + this.state);
}
return "NTLM " + response;
}
@Override
public boolean isComplete() {
return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED;
}
@Deprecated
@Override
public String getID() {
return null;
}
@Deprecated
@Override
public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException {View on GitHub (pinned to 9d1970a436)
Solutions
- Create a fresh ZapNTLMScheme (or call its constructor/reset path) for each authentication handshake instead of reusing an instance whose state machine already advanced.
- Ensure the standard HttpClient auth flow is used so processChallenge() is invoked with the server's NTLM challenge before authenticate() is called.
- Do not call authenticate() again after a successful Type 3 message; if the server re-challenges, the HttpClient should process the new challenge on the scheme instance.
- Log/inspect this.state at the call site to determine which out-of-sequence call is hitting the else branch.
Example fix
// before: reusing a scheme instance across requests
ZapNTLMScheme scheme = new ZapNTLMScheme();
scheme.processChallenge("NTLM");
String h1 = scheme.authenticate(creds, method);
String h2 = scheme.authenticate(creds, method); // throws Unexpected state: MSG_TYPE1_GENERATED
// after: fresh scheme per handshake
String h2 = new ZapNTLMScheme().withChallengeProcessed(); // or recreate and processChallenge() before authenticate() Defensive patterns
Strategy: try-catch
Validate before calling
if (scheme.isComplete()) {
scheme = new ZapNTLMScheme(); // reset state machine before re-authenticating
}
// ensure a challenge was processed:
// authenticate() only valid in CHALLENGE_RECEIVED or MSG_TYPE2_RECEVIED states Try / catch
try {
String header = scheme.authenticate(credentials, method);
} catch (AuthenticationException e) {
// e.g. "Unexpected state: ..." — recreate scheme and restart handshake
scheme = new ZapNTLMScheme();
// retry the request so processChallenge() runs first
} Prevention
- Never reuse a ZapNTLMScheme instance across authentication handshakes; let the HttpClient create/manage it per auth scope.
- Always call processChallenge() with the server's NTLM challenge before authenticate().
- Do not call authenticate() a second time after a Type 3 message was generated; start a fresh handshake instead.
- When manually driving NTLM, track the state yourself and only call authenticate() twice (Type 1 then Type 3).
When it happens
Trigger: Calling authenticate() when the scheme state is UNINITIATED (no processChallenge() call yet), already MSG_TYPE3_GENERATED (auth finished and authenticate() called again), MSG_TYPE1_GENERATED, or MSG_TYPE_GENERATED — i.e., calling authenticate() twice without a new challenge, or calling it before the server sent an NTLM challenge.
Common situations: Reusing a cached HttpClient/AuthScheme instance across requests so the state machine carries over a completed handshake; a misconfigured proxy or custom auth flow that calls authenticate() manually without processing the server's WWW-Authenticate: NTLM challenge first; intercepting/retrying requests and re-invoking the same scheme object.
Related errors
- Invalid NTLM challenge: {challenge}
- Out of sequence NTLM response message
- NTLM authentication failed
- Wrong signature
- NTLM type {expectedType} message expected - instead got type
AI-assisted analysis of zaproxy/zaproxy@9d1970a436 (2026-09-05).
Data as JSON: /api/errors/bffe9a13d013db79.
Report an issue: GitHub.