argon2-password
v1.0.0
Published
๐ The Bcrypt Successor โ Modern password hashing with Argon2id, smart pepper management, and zero-config security. No character limits. No GPU cracking. No compromises.
Maintainers
Readme
Why Argon Fortress?
| Feature | bcryptjs | argon2-password |
| --- | --- | --- |
| Algorithm | Bcrypt (1999) | Argon2id (2015 PHC Winner) |
| Password length | โ Limited to 72 chars | โ
Unlimited |
| GPU/ASIC resistance | โ ๏ธ Moderate | โ
Maximum (memory-hard) |
| Secret Pepper | โ Manual setup | โ
Smart auto-loading |
| Zero-config | โ Requires setup | โ
Works out of the box |
| ESM + CJS | โ ๏ธ Varies | โ
Dual module support |
Argon2id is the winner of the Password Hashing Competition and is recommended by OWASP for all new applications.
๐ฆ Installation
npm install argon2-passwordyarn add argon2-passwordpnpm add argon2-password๐ Quick Start
import { secureHash, secureVerify, isStrong } from 'argon2-password';
// 1. Validate the password
const check = isStrong('MyP@ssw0rd!123');
if (!check.strong) {
console.log('Weak password:', check.errors);
process.exit(1);
}
// 2. Hash it (zero-config โ works immediately!)
const hash = await secureHash('MyP@ssw0rd!123');
// => "$argon2id$v=19$m=65536,t=4,p=2$..."
// 3. Verify it later
const isValid = await secureVerify('MyP@ssw0rd!123', hash);
// => trueThat's it. No configuration files. No environment variables required. It just works.
๐ Smart Pepper โ Dual-Layer Security
Argon Fortress uses a "Smart Loader" to automatically find and apply a secret pepper to every hash. This adds an extra layer of security on top of the salt that Argon2 already generates.
How the Smart Loader Works
The pepper is resolved in this priority order:
1. ๐ฅ Pepper passed directly in function arguments
2. ๐ฅ process.env.AUTH_SECRET environment variable
3. ๐ฅ Machine-derived fallback (development convenience โ NOT for production)โ ๏ธ The fallback pepper is derived from your machine's hostname, OS user, architecture, and platform. It ensures the library never crashes without config, but it is not a substitute for a real secret. Always set
AUTH_SECRETin production.
// Priority 1 โ Explicit pepper (highest priority)
const hash = await secureHash('password', { pepper: 'my-secret' });
// Priority 2 โ Uses process.env.AUTH_SECRET automatically
const hash = await secureHash('password');
// Priority 3 โ Uses machine-derived fallback if nothing else is set
// (works, but NOT recommended for production)
const hash = await secureHash('password');Detecting Fallback Mode
Use isFallbackPepperActive() in your app startup to warn when no real secret is configured:
import { isFallbackPepperActive } from 'argon2-password';
if (isFallbackPepperActive()) {
console.warn(
'โ ๏ธ [argon2-password] No AUTH_SECRET set โ using fallback pepper.',
'Set AUTH_SECRET in your environment for production security.',
);
}๐ก๏ธ Setting Up Your Secret Key (Optional but Recommended)
To make your passwords virtually impossible to crack โ even if your entire database is leaked โ add a private secret key to your server.
Step 1: Create a .env file
Create a file named .env in your project's root folder:
# โ ๏ธ Change this to something unique and random for your app!
AUTH_SECRET=p@ss_W0rd_S3cr3t_99_XYZ_KeepThisPrivateStep 2: Load it in your app
Use a package like dotenv to load your .env file:
import 'dotenv/config'; // Load .env at the very top
import { secureHash, secureVerify } from 'argon2-password';
// AUTH_SECRET is now automatically used as the pepper!
const hash = await secureHash(userPassword);Step 3: Add .env to your .gitignore
# NEVER commit your secret key to source control!
.envWhy should I do this?
| Setup | Security Level | What happens if DB is leaked? |
| --- | --- | --- |
| Without .env | ๐ Moderate | Argon2id is still very hard to brute-force, but the fallback pepper is machine-derived โ an attacker on the same machine could reconstruct it. Not recommended for production. |
| With .env | ๐ข Maximum | Passwords are hashed with your private, app-specific key. Even if a hacker steals your database AND your source code, they cannot crack the passwords without this specific .env file from your server. |
๐ API Reference
secureHash(password, options?)
Hash a plaintext password using Argon2id.
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| password | string | โ
| The plaintext password to hash |
| options.pepper | string | โ | Custom pepper (overrides env/fallback) |
| options.memoryCost | number | โ | Memory in KiB, 1024โ4194304 (default: 65536 = 64MB) |
| options.timeCost | number | โ | Iterations, 1โ100 (default: 4) |
| options.parallelism | number | โ | Parallelism, 1โ16 (default: 2) |
| options.hashLength | number | โ | Hash length in bytes, 4โ128 (default: 32) |
Throws: ArgonOptionsError if any tuning parameter is outside the allowed range.
Returns: Promise<string> โ The encoded Argon2id hash.
const hash = await secureHash('MyP@ssw0rd!123');
const hash2 = await secureHash('MyP@ssw0rd!123', {
pepper: 'custom-key',
memoryCost: 131072, // 128 MB
timeCost: 6,
});secureVerify(password, hash, options?)
Verify a plaintext password against a stored Argon2id hash.
Tuning parameters (memory, time, parallelism, hashLength) are embedded in the hash itself โ only pepper is needed for verification.
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| password | string | โ
| The plaintext password to verify |
| hash | string | โ
| The stored Argon2id hash string |
| options.pepper | string | โ | Must match the pepper used during hashing |
Returns: Promise<boolean> โ true if valid, false otherwise.
Throws: VerificationError if an operational error occurs (native binding failure, OOM, etc.). A password mismatch or malformed hash returns false without throwing.
import { secureVerify, VerificationError } from 'argon2-password';
try {
const isValid = await secureVerify('MyP@ssw0rd!123', storedHash);
} catch (err) {
if (err instanceof VerificationError) {
console.error('System error during verification:', err.message);
// err.cause contains the original argon2 error
}
}โ ๏ธ Important: You must use the same pepper for verification that was used during hashing. If you change your
AUTH_SECRET, old hashes will no longer verify.
isStrong(password)
Validate password strength. Script-agnostic โ adapts based on the characters used:
- Cased scripts (Latin, Cyrillic, Greek): Requires mixed case.
- Caseless scripts (CJK, Arabic, Thai): Case requirements waived.
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| password | string | โ
| The password to evaluate |
Returns: { strong: boolean, errors: string[] }
| Rule | Requirement |
| --- | --- |
| Minimum length | At least 10 characters (by codepoint) |
| Letter | At least 1 Unicode letter (\p{L}) |
| Mixed case | Required only if cased characters are present |
| Digit | At least 1 digit (any script) |
| Special character | At least 1 non-letter, non-digit character |
isStrong('MyP@ssw0rd!123'); // { strong: true, errors: [] }
isStrong('ๅฏ็ ๅพๅผบ็ๆต่ฏๅฎๅ
จๅฅฝ1!'); // { strong: true, errors: [] }
isStrong('abc123'); // { strong: false, errors: [...] }๐๏ธ Full Example โ Express.js Auth
import 'dotenv/config';
import express from 'express';
import { secureHash, secureVerify, isStrong } from 'argon2-password';
const app = express();
app.use(express.json());
// Sign Up
app.post('/register', async (req, res) => {
const { email, password } = req.body;
// Validate password strength
const strength = isStrong(password);
if (!strength.strong) {
return res.status(400).json({ errors: strength.errors });
}
// Hash and store
const hash = await secureHash(password);
// await db.users.create({ email, passwordHash: hash });
res.json({ message: 'Account created!' });
});
// Login
app.post('/login', async (req, res) => {
const { email, password } = req.body;
// const user = await db.users.findByEmail(email);
const user = { passwordHash: '...' }; // placeholder
const isValid = await secureVerify(password, user.passwordHash);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
res.json({ message: 'Login successful!' });
});
app.listen(3000);๐ฌ How It Works Under the Hood
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ User's Password โ
โ "MyP@ssw0rd!123" โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ Smart Pepper Loader โ
โ โ
โ 1. Check function args โ pepper found? Use it. โ
โ 2. Check AUTH_SECRET โ env var set? Use it. โ
โ 3. Machine-derived fallback โ dev convenience only. โ
โ โ
โ Pepper โ SHA-256 โ 32-byte secret key โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ๏ธ Argon2id Engine โ
โ โ
โ โข Memory: 64 MB (resists GPU attacks) โ
โ โข Iterations: 4 (resists brute-force) โ
โ โข Parallelism: 2 (uses multi-core) โ
โ โข Salt: auto (unique per hash) โ
โ โข Secret: pepper (from Smart Loader) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ฆ Output โ
โ "$argon2id$v=19$m=65536,t=4,p=2$<salt>$<hash>" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ๏ธ Module Support
This package ships as both ESM and CommonJS, so it works everywhere:
// ESM (import)
import { secureHash, secureVerify, isStrong } from 'argon2-password';
// CommonJS (require)
const { secureHash, secureVerify, isStrong } = require('argon2-password');๐งช Testing
npm test๐ License
MIT ยฉ Deepak Kumar
