Almost every auth tutorial, including some of my own examples in earlier posts, uses bcrypt.hash(password, 10) without much explanation of what that number actually controls or why it's 10 specifically rather than some other value. It's worth understanding, because the right number isn't fixed, it's a moving target that most codebases never revisit after the initial copy-paste.
What the Salt Rounds Number Actually Controls
bcrypt's cost factor, the second argument, controls how computationally expensive hashing a single password is, specifically by determining how many times an internal key-setup routine repeats. It's not a linear scale, each increment roughly doubles the computation required. A cost factor of 10 means roughly 1,024 iterations of that internal routine. 11 means roughly 2,048. 12 means roughly 4,096.
This deliberate slowness is the entire point. A fast hash function is great for most use cases and terrible for passwords specifically, since a fast hash lets an attacker who's obtained a database of hashed passwords try billions of guesses per second against them. A deliberately slow hash function directly limits how many guesses an attacker can attempt in any given amount of time, even with significant computing power.
Why 10 Specifically, and Why That's Worth Questioning
The number 10 shows up constantly in tutorials and documentation because it was, at the time much of that content was written, a reasonable balance, slow enough to meaningfully limit brute-force attempts, fast enough not to noticeably slow down a real login flow for a legitimate user. That balance point depends entirely on how fast the hardware attempting to brute-force it actually is, and hardware, particularly specialized hardware built for exactly this kind of computation, gets faster every year. A cost factor considered a reasonable balance several years ago represents meaningfully less real protection against modern hardware than it did when that number first became the common default.
Why This Doesn't Show Up as an Obvious Problem
There's no error, no warning, no visible symptom of a salt rounds value that's become outdated relative to current hardware capability. Login continues working exactly as expected for legitimate users. The gap is entirely about resistance to an offline brute-force attempt against a stolen password database, something that only becomes relevant if that database is ever actually compromised, which means the weakness sits completely invisible until the exact moment it matters most.
The Actual Guidance, and Why It's a Moving Target Rather Than a Fixed Number
Security guidance on this specific number gets revised periodically as hardware capability changes, and current recommendations from security-focused organizations trend higher than the value that shows up in most existing tutorials and codebases. Rather than citing a specific number here that will itself become outdated, the actual, durable guidance is this, check current recommendations specifically when setting this up, rather than trusting whatever number happens to be sitting in the tutorial or codebase you're referencing, since that number is a snapshot of a past moment, not a permanently correct value.
A Practical Way to Choose the Right Number for Your Own Server
// A quick, practical way to find a cost factor appropriate for your actual hardware,
// rather than trusting a number copied from somewhere else
import bcrypt from 'bcryptjs';
async function benchmarkCostFactor(rounds: number) {
const start = Date.now();
await bcrypt.hash('benchmark-password', rounds);
return Date.now() - start;
}
// Run this on your actual production server or equivalent hardware,
// and pick the highest cost factor where a single hash still completes
// in a reasonable time for your login flow, commonly cited targets
// are in the range of a few hundred milliseconds
This gives you a real, current answer specific to your actual infrastructure, rather than a number that was correct for someone else's hardware at some point in the past and has simply been carried forward through tutorials and copy-paste ever since.
The Broader Rule This Points To
A security parameter borrowed from a tutorial is a snapshot of what was reasonable when that tutorial was written, not a permanently correct value. This applies beyond just bcrypt cost factors, TLS cipher suite choices, JWT expiration windows, rate limit thresholds, all of these are calibrated against assumptions, about hardware, about attacker capability, about acceptable risk, that shift over time. A value copied once and never revisited quietly drifts from "reasonable" toward "outdated" without any code change ever signaling that drift.
What to Actually Do About Your Own Codebase
Check what cost factor your app is actually using right now, and compare it against current guidance rather than assuming a number from an older tutorial or your own original setup is still appropriate. If it's genuinely outdated, increasing it is straightforward for new passwords going forward, though existing stored hashes were created with the old cost factor and remain at that lower value unless you specifically implement a rehash-on-next-login pattern, checking and upgrading a user's stored hash cost factor the next time they successfully log in with their correct password.
Check what cost factor your own app is actually using, and when you last revisited that number versus just inheriting it from the original setup or a tutorial. Drop what you find in the comments, genuinely curious how many people have actually reconsidered this since first setting it up versus never touching it again.
Get the templates: https://pixelanas.gumroad.com
Anas, full-stack Next.js developer building SaaS products and premium templates. X: @ASheikh69751
Top comments (0)