Follow these best practices to build secure, performant, and user-friendly authentication systems with ValidAuth.
Security Best Practices
Always Validate on Server Side
Never rely solely on client-side validation. Always validate authentication data on the server to prevent malicious users from bypassing client-side checks.
// ✅ Good: Validate on both client and server
// Client-side (React)
function LoginForm () {
const handleSubmit = async ( formData ) => {
// Client-side validation for UX
const clientValidation = validateLogin ( formData );
if ( ! clientValidation . valid ) {
showErrors ( clientValidation . errors );
return ;
}
// Send to server
const response = await fetch ( '/api/login' , {
method: 'POST' ,
body: JSON . stringify ( formData )
});
};
}
// Server-side (Express)
app . post ( '/api/login' , ( req , res ) => {
// Server-side validation for security
const serverValidation = validateLogin ( req . body );
if ( ! serverValidation . valid ) {
return res . status ( 400 ). json ({ errors: serverValidation . errors });
}
// Proceed with authentication
authenticateUser ( req . body );
});
Use Strong Password Requirements
Enforce strict password policies to protect user accounts.
import { isPassword } from 'validauth' ;
// ✅ Good: Strong password requirements
const passwordValidation = isPassword ( password , {
minLength: 12 , // Longer passwords are more secure
maxLength: 128 , // Prevent denial-of-service attacks
requireUppercase: true ,
requireLowercase: true ,
requireNumbers: true ,
requireSymbols: true ,
forbidCommonPasswords: true , // Critical for security
details: true
});
// ❌ Bad: Weak requirements
const weakValidation = isPassword ( password , {
minLength: 6 ,
requireSymbols: false ,
forbidCommonPasswords: false // Leaves users vulnerable
});
The forbidCommonPasswords option checks against 1,000+ common passwords from breach databases. Always enable this in production.
Block Temporary Email Services
Prevent spam and fake accounts by blocking disposable email domains.
import { isEmail } from 'validauth' ;
// ✅ Good: Block temporary email providers
const blockedDomains = [
'tempmail.com' ,
'throwaway.email' ,
'10minutemail.com' ,
'guerrillamail.com' ,
'mailinator.com' ,
'trashmail.com'
];
const emailValidation = isEmail ( email , {
blockedDomains ,
requireTLD: true ,
details: true
});
Protect Against Username-Password Correlation
Prevent users from creating passwords that contain their username or email.
import { isPassword } from 'validauth' ;
function validatePassword ( password , username , email ) {
// Standard validation
const result = isPassword ( password , {
minLength: 12 ,
requireSymbols: true ,
forbidCommonPasswords: true ,
details: true
});
if ( ! result . valid ) {
return result ;
}
// Additional security checks
const passwordLower = password . toLowerCase ();
const usernameLower = username . toLowerCase ();
const emailLocal = email . split ( '@' )[ 0 ]. toLowerCase ();
if ( passwordLower . includes ( usernameLower )) {
return {
valid: false ,
errors: [ 'Password cannot contain your username' ]
};
}
if ( passwordLower . includes ( emailLocal )) {
return {
valid: false ,
errors: [ 'Password cannot contain your email address' ]
};
}
return { valid: true };
}
Implement Rate Limiting
Protect against brute-force attacks by limiting validation attempts.
import { validateOTP } from 'validauth' ;
// Rate limiting example for OTP verification
class RateLimiter {
constructor () {
this . attempts = new Map ();
}
checkRateLimit ( identifier , maxAttempts = 5 , windowMs = 15 * 60 * 1000 ) {
const now = Date . now ();
const userAttempts = this . attempts . get ( identifier ) || [];
// Remove old attempts outside the time window
const recentAttempts = userAttempts . filter ( time => now - time < windowMs );
if ( recentAttempts . length >= maxAttempts ) {
return {
allowed: false ,
retryAfter: windowMs - ( now - recentAttempts [ 0 ])
};
}
// Record this attempt
recentAttempts . push ( now );
this . attempts . set ( identifier , recentAttempts );
return { allowed: true };
}
}
const limiter = new RateLimiter ();
function handleLogin ( email , password ) {
const rateCheck = limiter . checkRateLimit ( email );
if ( ! rateCheck . allowed ) {
return {
success: false ,
error: 'Too many attempts. Please try again later.' ,
retryAfter: rateCheck . retryAfter
};
}
// Proceed with validation
// ...
}
Use Simple Validation for Login
Login forms should use minimal validation rules for better performance and UX. Save strict validation for registration and password changes.
import { isEmail , isPassword } from 'validauth' ;
// ✅ Good: Lightweight validation for login
function validateLoginCredentials ( email , password ) {
const emailValid = isEmail ( email , {
requireTLD: true ,
details: false // Simple boolean response
});
const passwordValid = password . length > 0 ;
return emailValid && passwordValid ;
}
// ❌ Bad: Excessive validation for login
function overValidateLogin ( email , password ) {
const emailValid = isEmail ( email , {
allowPlusAddressing: false ,
blockedDomains: [ ... ], // Unnecessary for login
details: true
});
const passwordValid = isPassword ( password , {
minLength: 12 ,
requireSymbols: true ,
forbidCommonPasswords: true // Too slow for login
});
}
Cache Validation Results
Avoid redundant validation by caching results for unchanged input.
class CachedValidator {
constructor () {
this . cache = new Map ();
}
validateEmail ( email , options ) {
const cacheKey = ` ${ email } - ${ JSON . stringify ( options ) } ` ;
if ( this . cache . has ( cacheKey )) {
return this . cache . get ( cacheKey );
}
const result = isEmail ( email , options );
this . cache . set ( cacheKey , result );
// Limit cache size
if ( this . cache . size > 1000 ) {
const firstKey = this . cache . keys (). next (). value ;
this . cache . delete ( firstKey );
}
return result ;
}
}
Debounce Real-Time Validation
Reduce validation frequency for better performance in real-time scenarios.
import { isPassword , getPasswordStrength } from 'validauth' ;
// Debounce helper
function debounce ( func , wait ) {
let timeout ;
return function executedFunction ( ... args ) {
const later = () => {
clearTimeout ( timeout );
func ( ... args );
};
clearTimeout ( timeout );
timeout = setTimeout ( later , wait );
};
}
// ✅ Good: Debounced real-time validation
const debouncedValidation = debounce (( password , callback ) => {
const strength = getPasswordStrength ( password , { details: true });
const validation = isPassword ( password , {
minLength: 10 ,
requireSymbols: true ,
details: true
});
callback ({ strength , validation });
}, 300 ); // Wait 300ms after user stops typing
// Usage in React
function PasswordInput () {
const [ feedback , setFeedback ] = useState ( null );
const handleChange = ( e ) => {
const password = e . target . value ;
debouncedValidation ( password , setFeedback );
};
return < input type = "password" onChange = { handleChange } /> ;
}
Tree-Shake Unused Validators
Import only the validators you need to reduce bundle size.
// ✅ Good: Import only what you need
import { isEmail , isPassword } from 'validauth' ;
// ❌ Bad: Import everything
import * as validauth from 'validauth' ;
isEmail: ~4.4KB
isPassword: ~6KB (includes common passwords list)
isUsername: ~3.6KB
getPasswordStrength: ~5KB
Full library: ~14KB minified, ~5KB gzipped
Error Handling Best Practices
Provide Specific Error Messages
Use the details: true option to give users actionable feedback.
import { isPassword } from 'validauth' ;
// ✅ Good: Detailed errors help users fix issues
const result = isPassword ( password , {
minLength: 12 ,
requireSymbols: true ,
details: true
});
if ( ! result . valid ) {
// Display specific errors
result . errors . forEach ( error => {
showError ( error ); // "Password must be at least 12 characters long"
});
}
// ❌ Bad: Generic error message
if ( ! isPassword ( password )) {
showError ( 'Invalid password' ); // Not helpful
}
Handle Validation Errors Gracefully
Provide a good user experience even when validation fails.
function displayValidationErrors ( errors ) {
// Group errors by field
const errorsByField = {};
Object . keys ( errors ). forEach ( field => {
errorsByField [ field ] = Array . isArray ( errors [ field ])
? errors [ field ]
: [ errors [ field ]];
});
// Display inline errors
Object . keys ( errorsByField ). forEach ( field => {
const inputElement = document . getElementById ( field );
const errorElement = document . getElementById ( ` ${ field } -error` );
if ( inputElement && errorElement ) {
inputElement . classList . add ( 'error' );
errorElement . textContent = errorsByField [ field ][ 0 ];
errorElement . style . display = 'block' ;
}
});
}
For login validation, avoid revealing whether the email exists. Use generic error messages to prevent user enumeration attacks.
// ✅ Good: Generic error message for login
function authenticateUser ( email , password ) {
const user = findUserByEmail ( email );
if ( ! user || ! verifyPassword ( user , password )) {
// Don't reveal if email exists
return {
success: false ,
error: 'Invalid email or password'
};
}
return { success: true , user };
}
// ❌ Bad: Reveals which field is wrong
function badAuthentication ( email , password ) {
const user = findUserByEmail ( email );
if ( ! user ) {
return { error: 'Email not found' }; // User enumeration risk
}
if ( ! verifyPassword ( user , password )) {
return { error: 'Incorrect password' };
}
}
User Experience Guidelines
Show Password Strength in Real-Time
Help users create strong passwords with live feedback.
import { getPasswordStrength } from 'validauth' ;
function updatePasswordStrengthUI ( password ) {
const strength = getPasswordStrength ( password , { details: true });
// Update strength bar
const strengthBar = document . getElementById ( 'strength-bar' );
strengthBar . style . width = ` ${ strength . score } %` ;
strengthBar . className = `strength-bar ${ strength . strength } ` ;
// Update text feedback
const strengthText = document . getElementById ( 'strength-text' );
strengthText . textContent = `Strength: ${ strength . strength . toUpperCase () } ` ;
// Show crack time estimate
const crackTime = document . getElementById ( 'crack-time' );
crackTime . textContent = `Would take ${ strength . crackTimeDisplay } to crack` ;
}
Progressive Enhancement for Validation
Validate progressively as users complete fields.
// Validate on blur (when user leaves field)
function setupProgressiveValidation () {
const emailInput = document . getElementById ( 'email' );
const passwordInput = document . getElementById ( 'password' );
const usernameInput = document . getElementById ( 'username' );
emailInput . addEventListener ( 'blur' , () => {
const result = isEmail ( emailInput . value , { details: true });
if ( ! result . valid ) {
showFieldError ( 'email' , result . errors [ 0 ]);
} else {
clearFieldError ( 'email' );
showFieldSuccess ( 'email' );
}
});
passwordInput . addEventListener ( 'input' , debounce (() => {
const result = isPassword ( passwordInput . value , {
minLength: 10 ,
details: true
});
if ( passwordInput . value . length > 0 ) {
updatePasswordStrength ( passwordInput . value );
}
}, 300 ));
usernameInput . addEventListener ( 'blur' , async () => {
const result = isUsername ( usernameInput . value , { details: true });
if ( ! result . valid ) {
showFieldError ( 'username' , result . errors [ 0 ]);
} else {
// Check availability with server
const available = await checkUsernameAvailability ( usernameInput . value );
if ( available ) {
showFieldSuccess ( 'username' );
} else {
showFieldError ( 'username' , 'Username already taken' );
}
}
});
}
Provide Clear Password Requirements
Display requirements upfront so users know what’s expected.
function showPasswordRequirements () {
return `
<div class="password-requirements">
<p>Password must:</p>
<ul>
<li>Be at least 12 characters long</li>
<li>Contain at least one uppercase letter (A-Z)</li>
<li>Contain at least one lowercase letter (a-z)</li>
<li>Contain at least one number (0-9)</li>
<li>Contain at least one special character (!@#$%^&*)</li>
<li>Not be a commonly used password</li>
</ul>
</div>
` ;
}
Handle Edge Cases Gracefully
What if a user needs special characters in their username?
While ValidAuth defaults to disallowing special characters in usernames, you can enable them: const result = isUsername ( username , {
allowSpecialChars: true ,
minLength: 3 ,
maxLength: 30
});
However, consider the implications:
Special characters can complicate URL routing (e.g., /user/@john#doe)
They may be used for phishing (e.g., admin@site)
They can cause database or API issues
If you allow them, sanitize and escape properly in all contexts.
Should I allow plus addressing in emails?
Plus addressing (e.g., [email protected] ) has pros and cons: Allow it for:
User convenience (email filtering, tracking)
Legitimate use cases (newsletter management)
Block it for:
Preventing multiple accounts from same email
Reducing spam account creation
Stricter account verification
// For registration: block plus addressing
isEmail ( email , { allowPlusAddressing: false });
// For newsletter signup: allow it
isEmail ( email , { allowPlusAddressing: true });
How should I handle password validation during login vs registration?
Use different validation levels: // Registration: strict validation
function validateRegistrationPassword ( password ) {
return isPassword ( password , {
minLength: 12 ,
requireSymbols: true ,
forbidCommonPasswords: true ,
details: true
});
}
// Login: minimal validation
function validateLoginPassword ( password ) {
// Just check it's not empty
return password . length > 0 ;
}
Never validate password strength during login - it degrades performance and provides no security benefit.
Common Pitfalls to Avoid
Don’t Store Validation Results in Passwords
// ❌ Bad: Never store validation metadata with passwords
const user = {
password: hashPassword ( password ),
passwordStrength: getPasswordStrength ( password ), // Don't do this
};
// ✅ Good: Only store the hashed password
const user = {
password: hashPassword ( password )
};
Don’t Skip Validation for “Trusted” Sources
// ❌ Bad: Skipping validation for admin users
if ( user . role !== 'admin' ) {
validatePassword ( password );
}
// ✅ Good: Validate all input regardless of source
const validation = validatePassword ( password );
if ( ! validation . valid ) {
throw new Error ( 'Invalid password' );
}
Don’t Use Only Client-Side Validation
// ❌ Bad: Only validating on frontend
function handleSignup ( formData ) {
if ( validateForm ( formData )) {
// Send directly to database without server validation
saveUser ( formData );
}
}
// ✅ Good: Validate on both client and server
function handleSignup ( formData ) {
// Client validation
if ( ! validateFormClientSide ( formData )) {
showErrors ();
return ;
}
// Send to server for server-side validation
fetch ( '/api/signup' , {
method: 'POST' ,
body: JSON . stringify ( formData )
});
}
// Server endpoint
app . post ( '/api/signup' , ( req , res ) => {
// Server validation (critical!)
if ( ! validateFormServerSide ( req . body )) {
return res . status ( 400 ). json ({ error: 'Invalid data' });
}
saveUser ( req . body );
});
Configuration Recommendations
Production Settings
Recommended ValidAuth configuration for production environments:
const productionConfig = {
email: {
allowPlusAddressing: false ,
requireTLD: true ,
blockedDomains: [
'tempmail.com' ,
'throwaway.email' ,
'10minutemail.com' ,
'guerrillamail.com' ,
'mailinator.com'
],
details: true
},
password: {
minLength: 12 ,
maxLength: 128 ,
requireUppercase: true ,
requireLowercase: true ,
requireNumbers: true ,
requireSymbols: true ,
forbidCommonPasswords: true ,
details: true
},
username: {
minLength: 4 ,
maxLength: 20 ,
allowSpecialChars: false ,
forbidSpaces: true ,
forbidStartingNumber: true ,
blockedUsernames: [
'admin' , 'administrator' , 'root' , 'system' ,
'moderator' , 'mod' , 'support' , 'help' ,
'official' , 'staff' , 'owner'
],
details: true
}
};
Development Settings
More lenient configuration for development and testing:
const developmentConfig = {
email: {
allowPlusAddressing: true ,
requireTLD: false , // Allow localhost emails
blockedDomains: [],
details: true
},
password: {
minLength: 6 ,
maxLength: 128 ,
requireUppercase: false ,
requireLowercase: false ,
requireNumbers: false ,
requireSymbols: false ,
forbidCommonPasswords: false , // Allow weak passwords for testing
details: true
},
username: {
minLength: 2 ,
maxLength: 50 ,
allowSpecialChars: true ,
forbidSpaces: false ,
forbidStartingNumber: false ,
blockedUsernames: [],
details: true
}
};
Use environment variables to switch between production and development configurations: const config = process . env . NODE_ENV === 'production'
? productionConfig
: developmentConfig ;
Summary
Security First
Always validate on server side
Use strong password requirements with forbidCommonPasswords: true
Block temporary email domains
Implement rate limiting
Don’t leak information in error messages
Optimize Performance
Use simple validation for login forms
Cache validation results when appropriate
Debounce real-time validation
Import only the validators you need
Enhance User Experience
Show password strength in real-time
Provide specific, actionable error messages
Display requirements upfront
Use progressive validation
Handle edge cases gracefully
Follow Best Practices
Use different validation rules for registration vs login
Validate all input regardless of source
Store only hashed passwords, never validation metadata
Use environment-specific configurations