How to Hash Passwords in Node.js
Part of the Hashing Toolkitconst hash = await argon2.hash(password);the argon2 package — defaults to Argon2id with a random salt generated automatically
Explanation
The argon2 package (npm install argon2) is the current recommended choice for Node.js — it wraps the real, native Argon2 reference implementation, not a pure-JS reimplementation, and defaults to Argon2id with a random salt generated automatically. You never call a separate salt-generation function; it's baked into hash().
const argon2 = require("argon2");
const hash = await argon2.hash(password);
const ok = await argon2.verify(hash, password);Both functions are async and return Promises — hash() resolves to the full encoded hash string (algorithm, version, parameters, salt, and digest all packed together), and verify() resolves to a plain boolean, unpacking those embedded parameters from the hash itself rather than needing them passed in separately.
To set parameters explicitly instead of relying on the library's defaults — OWASP's baseline is m=19456, t=2, p=1:
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456,
timeCost: 2,
parallelism: 1,
});For the full mechanics of what each of these parameters actually controls, see Argon2 Explained.
Valid examples
const hash = await argon2.hash(password); // "$argon2id$v=19$m=65536,t=3,p=4$..."Defaults to Argon2id. The salt, algorithm, and every parameter are embedded in the returned string — nothing extra to store separately.
const ok = await argon2.verify(hash, password); // true or falseReads the parameters back out of hash itself, so verification always uses whatever settings the hash was actually created with.
await argon2.hash(password, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1 });Explicit OWASP-baseline parameters instead of the library defaults.
Invalid examples
const hash = argon2.hash(password); // missing awaithash() is async — without await, hash is a pending Promise object, not a string, and that's what gets saved to the database.
require("crypto").createHash("sha256").update(password).digest("hex")A raw SHA-256 digest — no salt, no cost factor, computable at billions per second on a GPU. See Why SHA-256 Should Not Be Used for Passwords.
if (hash === computeHash(password)) { /* login */ }Manually recomputing and comparing with === instead of calling verify() — skips the constant-time comparison the library provides, and won't work at all once the hash has an embedded salt.