brianc/node-postgres · error · Error
SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error
Error message
SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: "${error}" What it means
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).
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.
Example fix
// The error message includes the server's reason: // e.g. "SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: \"invalid-proof\"" // 'invalid-proof' almost always means wrong password — verify via psql: // PGPASSWORD=your_password psql -h host -U user -d db // Then use the confirmed password in your connection config
Defensive patterns
Strategy: try-catch
Validate before calling
// Verify credentials before opening a connection pool:
const probe = new Client(connStr)
try {
await probe.connect()
await probe.end()
} catch (e) {
console.error('Credentials invalid:', e.message)
} Try / catch
try {
await client.connect()
} catch (err) {
if (err.message.includes('server returned error')) {
// Extract the SCRAM error reason from the message
const match = err.message.match(/server returned error: "(.+)"/)
const scramError = match ? match[1] : 'unknown'
if (scramError === 'invalid-proof') {
throw new Error('Authentication rejected: wrong password')
}
throw new Error(`Authentication rejected by server: ${scramError}`)
}
throw err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not…
- SASL: Invalid attribute pair entry
- SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is…
- SASL: SCRAM-SERVER-FINAL-MESSAGE: serverData must be a…
- SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count
AI-assisted analysis of brianc/node-postgres@ff9d775abd (2026-08-11).
Data as JSON: /api/errors/4c91f49b1e897788.
Report an issue: GitHub.
Appendix: source
Thrown at packages/pg/lib/crypto/sasl.js:228
} else if (!/^[1-9][0-9]*$/.test(iterationText)) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count')
}
const iteration = parseInt(iterationText, 10)
return {
nonce,
salt,
iteration,
}
}
function parseServerFinalMessage(serverData) {
const attrPairs = parseAttributePairs(serverData)
const error = attrPairs.get('e')
const serverSignature = attrPairs.get('v')
if (error) {
throw new Error(`SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: "${error}"`)
}
if (!serverSignature) {
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing')
} else if (!isBase64(serverSignature)) {
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64')
}
return {
serverSignature,
}
}
function xorBuffers(a, b) {
if (!Buffer.isBuffer(a)) {
throw new TypeError('first argument must be a Buffer')
}
if (!Buffer.isBuffer(b)) {
throw new TypeError('second argument must be a Buffer')View on GitHub (pinned to ff9d775abd)