Verify a password without storing it
Maya registers with an email address and a password. The server needs to recognize that password later without storing a copy that anyone can read.
A password hash is a derived value. At login, the server applies the same verification process to the submitted password and compares the result with the stored record.
This differs from encryption. Encryption supports recovering the original value with a key. Password verification does not require recovering the password, so storing decryptable passwords adds unnecessary risk.
Passwords need a deliberately expensive hash
Human passwords are often predictable. If an attacker steals the database, they can guess passwords offline without asking the API. A fast general-purpose hash lets the attacker test many guesses.
Taskboard uses Node's asynchronous scrypt function with a random salt for each password record. Two people choosing the same password do not receive the same stored value.
The work cost makes each guess consume more resources. It also makes legitimate login verification cost resources, so login rate limits and bounded input lengths matter.
The stored record contains the algorithm label, salt, and derived hash. This implementation uses Node's default scrypt cost settings rather than storing custom parameters. A production change to those settings needs a versioned record format so older hashes remain verifiable. A salt does not need to remain secret. The password does.
This is the actual password derivation function. scryptAsync waits for the asynchronous cryptographic operation, and the return string keeps the salt with the derived value.
export async function hashPassword(password: string): Promise<string> {
const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(password, salt);
return `scrypt:${salt}:${Buffer.from(key).toString('hex')}`;
}
The login response must avoid extra disclosure
Returning "email does not exist" for one input and "wrong password" for another lets callers discover registered accounts. A generic invalid-credentials response reduces that direct disclosure.
Timing can also reveal differences. A path that skips expensive verification for unknown accounts may be noticeably faster. Security review must consider behavior, timing, logs, and rate limits together.
TLS protects passwords while they travel to the API. Password hashing protects stored password material if the database is exposed. Neither mechanism makes it safe to log login bodies.
A common mistake is hashing a password in the browser and treating that hash as harmless. If the server accepts the same hash as the credential, the hash becomes a reusable password equivalent.
Why can session tokens use a faster hash than passwords?
Session tokens contain high-entropy random bytes rather than human-chosen words. Attackers cannot feasibly guess that token space in the way they can guess common passwords.
Passwords need slow verification, safe transport, careful responses, and protection from accidental logging.
Read api/src/security.ts Read api/src/auth.ts