Crafting Strong Passwords:
Best Practices for Secure Signups

Published on: August 20, 2024

In this comprehensive guide, we’ll explore the foundational elements of secure authentication.
We’ll start with the best practices for creating and managing passwords,
delve into the importance of generating secure hashes,
and walk through the process of comparing hashes during login.

What makes a password secure?

A good strong password is essential for securing user accounts and protecting sensitive information.
According to the OWASP (Open Web Application Security Project) guidelines,
a strong password should adhere to several key principles to minimize the risk of unauthorized access.

Implementing password requirements

To ensure that our application adheres to strong password practices, we’ll be using the OWASP Password Strength Test library by NowSecure.
This library is designed to help enforce password requirements on the client side and validate passwords on the authentication service.

Example implementation in React
import owasp from 'owasp-password-strength-test';

owasp.config({
	allowPassphrases: true,
	maxLength: 128,
	minLength: 10,
	minPhraseLength: 20,
	minOptionalTestsToPass: 4,
});

const passwdStrength = owasp.test(value);
interface PasswordInputProps {
	title?: string;
	characterLimit?: number;
}

const PasswordInput = ({
	title = '',
	characterLimit = 128,
}: PasswordInputProps) => {
	const [value, setValue] = useState('');
	const passwdStrength = owasp.test(value);

	const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
		const { value } = e.target;
		if (value.length > characterLimit) return;
		setValue(value.replace(/[^+a-z0-9áéíóúñü_\-.!@#$%^&*(){}[\]]/gi, ''));
	};

	const isError = value.length > 0 && passwdStrength.errors.length > 0 || false;

	return (
		<div className={`${styles['password-input']} ${isError ? styles['password-input--error'] : ''}`}>
			<div className={styles['password-input__label']}>
				<p>{title}</p>
				<span>*</span>
				<cite>{`${value.length} / ${characterLimit}`}</cite>
			</div>
			<input
				tabIndex={0}
				placeholder="Enter your password"
				onChange={handleChange}
				type="password"
				value={value}
			/>
			<div className={styles['password-input__strength']}>
				<cite className="error">
					{value.length > 0 ? passwdStrength?.errors?.[0] : ''}
				</cite>
				<cite>{passwdStrength.strong ? 'Strong' : 'Weak'}</cite>
			</div>
		</div>
	);
};

export default PasswordInput;

Password

*0 / 128
Weak
Verify password strength on server-side

To ensure that the password meets the minimum requirements, we should also validate it on the server-side before hashing it.

We can use the same OWASP library to validate the password on the server-side as well.

Hashing passwords for secure storage

When storing passwords in a database, it’s crucial to hash them securely to prevent unauthorized access.
We’ll be using the bcrypt.js library to hash and compare passwords in our application.

Example implementation in Node.js
import bcrypt from 'bcrypt';

export const generateHash = (password: string) =>
	new Promise<string>((resolve, reject) => {
		bcrypt.hash(password, 10, (error, hash) => {
			if (error) {
				return reject(errors.HASHING_FAILED);
			}
			return resolve(hash);
		});
	});
The resulting hash looks something like this:
$2b$10$<22-character-salt><31-character-hash>

Bcrypt securely hashes passwords by generating a unique, random salt for each password, ensuring that even identical passwords result in different hashes, effectively protecting against rainbow table and brute-force attacks.


You can learn more about bcrypt here.

Comparing hashed passwords during login

When a user logs in, we need to compare the hashed password stored in the database with the hashed password entered by the user.

Example implementation in Node.js
import bcrypt from 'bcrypt';

export const verifyHash = (password: string, hash: string) =>
	new Promise<boolean>((resolve, reject) => {
		bcrypt.compare(password, hash, (error, result) => {
			if (error) {
				return reject(errors.HASH_VERIFICATION_FAILED);
			}
			return resolve(result);
		});
	});
Conclusion