DevTools Hub

Search tools

Search for a developer tool

How to Hash Passwords in Node.js

Part of the Hashing Toolkit
Pattern
const 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 false

    Reads 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 await

    hash() 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.

Try it now