{"record":{"id":"077b6f82014cbc3a","repo":"apache/cassandra","slug":"sasl-negotiation-not-complete","errorCode":null,"errorMessage":"SASL negotiation not complete","messagePattern":"SASL negotiation not complete","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/auth/PasswordAuthenticator.java","lineNumber":309,"sourceCode":"        private String username;\n        private String password;\n\n        public byte[] evaluateResponse(byte[] clientResponse) throws AuthenticationException\n        {\n            decodeCredentials(clientResponse);\n            complete = true;\n            return null;\n        }\n\n        public boolean isComplete()\n        {\n            return complete;\n        }\n\n        public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException\n        {\n            if (!complete)\n                throw new AuthenticationException(\"SASL negotiation not complete\");\n            return authenticate(username, password);\n        }\n\n        @Override\n        public AuthenticationMode getAuthenticationMode()\n        {\n            return AuthenticationMode.PASSWORD;\n        }\n\n        /**\n         * SASL PLAIN mechanism specifies that credentials are encoded in a\n         * sequence of UTF-8 bytes, delimited by 0 (US-ASCII NUL).\n         * The form is : {@code authzId<NUL>authnId<NUL>password<NUL>}\n         * authzId is optional, and in fact we don't care about it here as we'll\n         * set the authzId to match the authnId (that is, there is no concept of\n         * a user being authorized to act on behalf of another with this IAuthenticator).\n         *\n         * @param bytes encoded credentials string sent by the client","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java#L291-L327","documentation":"PasswordAuthenticator's SASLPlainAuthenticator tracks whether the full PLAIN SASL exchange (the client's response containing authzid/authcid/password) has been received. getAuthenticatedUser() refuses to return a user until evaluateResponse() has completed the negotiation and the authenticator state is 'complete'. Throwing AuthenticationException here prevents reading credentials before they were decoded.","triggerScenarios":"Calling SaslNegotiator.getAuthenticatedUser() before any call to evaluateResponse(), or after a negotiation that threw AuthenticationException (leaving complete=false). Typically from the native-protocol auth flow when the server asks for the authenticated user at the wrong lifecycle point.","commonSituations":"Custom client drivers skipping the SASL response step; middleware or test code instantiating PasswordAuthenticator.newSaslNegotiator() and directly querying the user; a failed/cancelled SASL handshake followed by a user lookup.","solutions":["Ensure the client completes the SASL PLAIN handshake: send the AuthResponse token (NUL-separated authzid/authcid/password) before the server queries the user","In server-side/custom code, call evaluateResponse(byte[]) first and only call getAuthenticatedUser() when it signals completion","Check driver authentication configuration (authenticator class, credentials provider) so the handshake is not aborted early"],"exampleFix":"// before\nAuthenticatedUser user = negotiator.getAuthenticatedUser(); // throws if handshake incomplete\n// after\nbyte[] response = buildPlainToken(user, pass);\nif (negotiator.evaluateResponse(response) instanceof AuthenticationException)\n    throw new IllegalStateException(\"SASL handshake failed\");\nAuthenticatedUser user = negotiator.getAuthenticatedUser();","handlingStrategy":"try-catch","validationCode":"if (!negotiatorComplete(negotiator)) throw new IllegalStateException(\"call evaluateResponse first\");","typeGuard":"boolean saslComplete(SaslNegotiator n) { return n != null && n.getAuthenticationMode() != null && completed(n); }","tryCatchPattern":"try { user = negotiator.getAuthenticatedUser(); } catch (AuthenticationException e) { handleAuthRequired(); }","preventionTips":["Always drive the SASL state machine in order: evaluateResponse then getAuthenticatedUser","Wrap client auth flows in the driver's built-in auth provider instead of hand-rolling SASL","Log negotiation state on failure to catch lifecycle misuse early"],"tags":["authentication","sasl","lifecycle"],"backgroundTag":"authentication-required","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}