{"record":{"id":"4c91f49b1e897788","repo":"brianc/node-postgres","slug":"sasl-scram-server-final-message-server-returned","errorCode":null,"errorMessage":"SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: \"${error}\"","messagePattern":"SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/pg/lib/crypto/sasl.js","lineNumber":228,"sourceCode":"  } else if (!/^[1-9][0-9]*$/.test(iterationText)) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count')\n  }\n  const iteration = parseInt(iterationText, 10)\n\n  return {\n    nonce,\n    salt,\n    iteration,\n  }\n}\n\nfunction parseServerFinalMessage(serverData) {\n  const attrPairs = parseAttributePairs(serverData)\n  const error = attrPairs.get('e')\n  const serverSignature = attrPairs.get('v')\n\n  if (error) {\n    throw new Error(`SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: \"${error}\"`)\n  }\n\n  if (!serverSignature) {\n    throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing')\n  } else if (!isBase64(serverSignature)) {\n    throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64')\n  }\n  return {\n    serverSignature,\n  }\n}\n\nfunction xorBuffers(a, b) {\n  if (!Buffer.isBuffer(a)) {\n    throw new TypeError('first argument must be a Buffer')\n  }\n  if (!Buffer.isBuffer(b)) {\n    throw new TypeError('second argument must be a Buffer')","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/brianc/node-postgres/blob/ff9d775abd12f29dd6df03945253b54eabbb29f2/packages/pg/lib/crypto/sasl.js#L210-L246","documentation":"Thrown by parseServerFinalMessage() when the server's final SASL message contains an e= attribute (error) instead of a v= attribute (verifier/signature). Per RFC 5802, a server that rejects the client's proof sends an error attribute rather than a signature. The embedded error string typically contains a SCRAM error indicator such as 'invalid-proof' (wrong password) or 'channel-binding' (channel binding mismatch).","triggerScenarios":"At sasl.js:224-228, attrPairs.get('e') is truthy. The server's final message includes an e= attribute, indicating it rejected the authentication. The specific error value is interpolated into the message string.","commonSituations":"Wrong password (the server computed a different proof and rejects the client's); the server-side role password was changed between the salt retrieval and the proof submission; channel binding configuration mismatch (client and server disagree on TLS channel binding); server-side authentication policy rejection.","solutions":["Verify the password is correct by connecting with psql using the same credentials.","If the error mentions channel-binding, verify SSL/TLS configuration matches between client and server.","Check whether the password was recently rotated on the server.","Inspect the full error message — the embedded error string (e.g., 'invalid-proof') indicates the specific rejection reason."],"exampleFix":"// The error message includes the server's reason:\n// e.g. \"SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: \\\"invalid-proof\\\"\"\n// 'invalid-proof' almost always means wrong password — verify via psql:\n//   PGPASSWORD=your_password psql -h host -U user -d db\n// Then use the confirmed password in your connection config","handlingStrategy":"try-catch","validationCode":"// Verify credentials before opening a connection pool:\nconst probe = new Client(connStr)\ntry {\n  await probe.connect()\n  await probe.end()\n} catch (e) {\n  console.error('Credentials invalid:', e.message)\n}","typeGuard":null,"tryCatchPattern":"try {\n  await client.connect()\n} catch (err) {\n  if (err.message.includes('server returned error')) {\n    // Extract the SCRAM error reason from the message\n    const match = err.message.match(/server returned error: \"(.+)\"/)\n    const scramError = match ? match[1] : 'unknown'\n    if (scramError === 'invalid-proof') {\n      throw new Error('Authentication rejected: wrong password')\n    }\n    throw new Error(`Authentication rejected by server: ${scramError}`)\n  }\n  throw err\n}","preventionTips":["Verify the password with psql before deploying.","Use a secrets manager to keep credentials current across deployments.","When the server rotates passwords, update all clients promptly.","If the error mentions channel-binding, verify TLS configuration matches.","Inspect the full error message for the embedded SCRAM error reason."],"tags":["authentication","sasl","scram","password","connection"],"backgroundTag":null,"analyzedSha":"ff9d775abd12f29dd6df03945253b54eabbb29f2","analyzedAt":"2026-08-11T15:33:59.644Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}