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.
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.
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.
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 / 128To 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.
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.
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);
});
});
$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.
When a user logs in, we need to compare the hashed password stored in the database with the hashed password entered by the user.
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);
});
});