Password Hashing

The Password Hashing component provides secure, battle-tested utilities for hashing and verifying user passwords.

It supports multiple algorithms so you can choose the right trade-off between security, performance, and compatibility.

Supported algorithms:

  • argon2 (recommended)
  • bcryptjs
  • scrypt
  • pbkdf2

Installation Guide

Install the component using the ServerCN CLI:

npx servercn add password-hashing

Basic Implementation

You can place the following helpers in your project:

MVC: src/helpers/auth.helpers.ts or src/helpers/auth.ts

Feature: src/modules/auth/auth.helpers.ts

Choose one password hashing strategy based on your security and performance requirements.

A modern, memory-hard algorithm designed to resist GPU and ASIC attacks. Best choice for new applications

import argon2 from "argon2";
 
export async function hashPassword(password: string): Promise<string> {
  return argon2.hash(password);
}
 
export async function verifyPassword(
  password: string,
  hash: string
): Promise<boolean> {
  return argon2.verify(hash, password);
}

A widely adopted and battle-tested algorithm with configurable cost factor. Suitable for legacy compatibility.

import bcrypt from "bcryptjs";
 
export async function hashPassword(
  password: string,
  rounds = 12
): Promise<string> {
  return bcrypt.hash(password, rounds);
}
 
export async function verifyPassword(
  password: string,
  hash: string
): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

A memory-intensive algorithm that significantly increases the cost of brute-force attacks.

import crypto from "node:crypto";
import { promisify } from "node:util";
 
const scryptAsync = promisify(
  crypto.scrypt as (
    password: crypto.BinaryLike,
    salt: crypto.BinaryLike,
    keylen: number,
    options: crypto.ScryptOptions,
    callback: (err: Error | null, derivedKey: Buffer) => void
  ) => void
);
 
const DEFAULTS = {
  keyLength: 64,
  saltLength: 16,
  N: 16384,
  r: 8,
  p: 1
};
 
export async function hashPasswordScrypt(
  password: string,
  options = DEFAULTS
): Promise<string> {
  const salt = crypto.randomBytes(options.saltLength);
 
  const derivedKey = await scryptAsync(password, salt, options.keyLength, {
    N: options.N,
    r: options.r,
    p: options.p
  });
 
  return [
    "scrypt",
    options.N,
    options.r,
    options.p,
    salt.toString("hex"),
    derivedKey.toString("hex")
  ].join("$");
}
 
export async function verifyPasswordScrypt(
  password: string,
  storedHash: string
): Promise<boolean> {
  const [algo, N, r, p, saltHex, hashHex] = storedHash.split("$");
 
  if (algo !== "scrypt") {
    throw new Error("Invalid scrypt hash format");
  }
 
  const salt = Buffer.from(saltHex, "hex");
  const hash = Buffer.from(hashHex, "hex");
 
  const derivedKey = await scryptAsync(password, salt, hash.length, {
    N: Number(N),
    r: Number(r),
    p: Number(p)
  });
 
  return crypto.timingSafeEqual(hash, derivedKey);
}

A standards-based algorithm included in Node.js core. Useful when external dependencies are not allowed.

import crypto from "node:crypto";
 
const DEFAULTS = {
  iterations: 310000,
  keyLength: 64,
  saltLength: 16,
  digest: "sha512"
};
 
export function hashPasswordPbkdf2(
  password: string,
  options = DEFAULTS
): string {
  const salt = crypto.randomBytes(options.saltLength);
 
  const derivedKey = crypto.pbkdf2Sync(
    password,
    salt,
    options.iterations,
    options.keyLength,
    options.digest
  );
 
  return [
    "pbkdf2",
    options.iterations,
    options.digest,
    salt.toString("hex"),
    derivedKey.toString("hex")
  ].join("$");
}
 
export function verifyPasswordPbkdf2(
  password: string,
  storedHash: string
): boolean {
  const [algo, iterations, digest, saltHex, hashHex] = storedHash.split("$");
 
  if (algo !== "pbkdf2") {
    throw new Error("Invalid PBKDF2 hash format");
  }
 
  const salt = Buffer.from(saltHex, "hex");
  const hash = Buffer.from(hashHex, "hex");
 
  const derivedKey = crypto.pbkdf2Sync(
    password,
    salt,
    Number(iterations),
    hash.length,
    digest
  );
 
  return crypto.timingSafeEqual(hash, derivedKey);
}

Recommendation

For most applications:

  • Use Argon2 for new projects
  • Use bcrypt only for compatibility with existing systems
  • Prefer scrypt or PBKDF2 when external dependencies are restricted

Usage Example

/**
 * The examples below demonstrate password hashing and verification
 * using the argon2-based helpers from the Password Hashing component.
 */
 
import { hashPassword, verifyPassword } from "../helpers/auth";
 
export const hashPasswordUsage = (req, res) => {
  const { password } = req.body;
  const hashedPassword = await hashPassword(password);
  return ApiResponse.ok(res, "Password hashed successfully");
};
 
export const verifyPasswordUsage = (req, res) => {
  const { email, password } = req.body;
 
  const user = await User.findOne({ email }).select("+password");
  if (!user) {
    throw ApiError.unauthorized("Invalid email or password");
  }
 
  const isPasswordValid = await verifyPassword(password, user.password);
  if (!isPasswordValid) {
    throw ApiError.unauthorized("Invalid email or password");
  }
 
  return ApiResponse.ok(res, "Password verified successfully");
};

File & Folder Structure

Select a file to view its contents

Installation

npx servercn add password-hashing