Secure Password Generator Component

Master machine coding by creating a secure password generator with customizable options, copy-to-clipboard functionality, and advanced security features.

Machine Coding Challenge: Password Generator Component

Create a production-ready password generator with customizable options, copy-to-clipboard functionality, and advanced security features using React and modern web APIs.


Problem Statement

Design and implement a secure password generator component with the following requirements:

✅ Core Features (Must Have)

  • Generate secure passwords with customizable options
  • Copy-to-clipboard functionality
  • Password strength indicator
  • Customizable password length
  • Multiple character set options (uppercase, lowercase, numbers, symbols)

Advanced Features (Nice to Have)

  • Password strength meter with visual feedback
  • Password history/previous passwords
  • Export passwords to file
  • Keyboard shortcuts support
  • Accessibility compliance
  • Mobile-responsive design

UI/UX Requirements

  • Clean, modern interface design
  • Visual feedback for all actions
  • Loading states and animations
  • Error handling and validation
  • Accessibility features (ARIA labels, keyboard navigation)

🧠 Clarifying Questions & Scope

Functional Requirements

  • Password Length: Minimum and maximum length? (8-128 characters?)
  • Character Sets: Which character sets to include?
  • Strength Meter: How to calculate and display password strength?
  • History: Should we store previous passwords?
  • Export: File format for password export?

Technical Constraints

  • Time Limit: 2-3 hours for implementation
  • Framework: React with hooks
  • Security: Use cryptographically secure random generation
  • Browser Support: Modern browsers with Clipboard API

Success Criteria

  • Generate cryptographically secure passwords
  • Smooth user experience with immediate feedback
  • Proper error handling and edge cases
  • Clean, maintainable code structure

🏗️ High-Level Architecture

1. Component Structure

PasswordGenerator/
├── PasswordGeneratorContainer/  # Main container component
│   ├── PasswordDisplay/        # Password output with copy button
│   ├── OptionsPanel/          # Character set and length options
│   ├── StrengthMeter/         # Password strength indicator
│   ├── GenerateButton/        # Generate password button
│   └── PasswordHistory/       # Previous passwords list
├── usePasswordGenerator/       # Custom hook for password logic
├── useClipboard/              # Clipboard API hook
└── usePasswordStrength/       # Password strength calculation

2. State Management Strategy

interface PasswordGeneratorState {
  password: string;
  options: PasswordOptions;
  strength: PasswordStrength;
  history: string[];
  isCopied: boolean;
  error: string | null;
}

interface PasswordOptions {
  length: number;
  uppercase: boolean;
  lowercase: boolean;
  numbers: boolean;
  symbols: boolean;
  excludeSimilar: boolean;
  excludeAmbiguous: boolean;
}

interface PasswordStrength {
  score: number; // 0-100
  label: string; // 'Weak', 'Medium', 'Strong', 'Very Strong'
  color: string; // CSS color for strength meter
}

🛠️ Step-by-Step Implementation

Step 1: Basic Password Generator Setup

// components/PasswordGenerator.tsx
import React, { useState, useCallback } from 'react';
import { usePasswordGenerator } from '../hooks/usePasswordGenerator';
import { useClipboard } from '../hooks/useClipboard';

interface PasswordGeneratorProps {
  defaultLength?: number;
  maxLength?: number;
  minLength?: number;
  showStrength?: boolean;
  showHistory?: boolean;
}

const PasswordGenerator: React.FC<PasswordGeneratorProps> = ({
  defaultLength = 12,
  maxLength = 128,
  minLength = 8,
  showStrength = true,
  showHistory = false
}) => {
  const [password, setPassword] = useState('');
  const [options, setOptions] = useState({
    length: defaultLength,
    uppercase: true,
    lowercase: true,
    numbers: true,
    symbols: true,
    excludeSimilar: false,
    excludeAmbiguous: false
  });

  const { generatePassword, strength } = usePasswordGenerator();
  const { copyToClipboard, isCopied } = useClipboard();

  const handleGeneratePassword = useCallback(() => {
    const newPassword = generatePassword(options);
    setPassword(newPassword);
  }, [generatePassword, options]);

  const handleCopyPassword = useCallback(() => {
    if (password) {
      copyToClipboard(password);
    }
  }, [copyToClipboard, password]);

  return (
    <div className="password-generator">
      <div className="password-generator-header">
        <h1>🔐 Secure Password Generator</h1>
        <p>Generate strong, secure passwords with customizable options</p>
      </div>

      <div className="password-generator-content">
        {/* Password Display */}
        <PasswordDisplay
          password={password}
          onCopy={handleCopyPassword}
          isCopied={isCopied}
        />

        {/* Strength Meter */}
        {showStrength && password && (
          <StrengthMeter strength={strength} />
        )}

        {/* Options Panel */}
        <OptionsPanel
          options={options}
          onOptionChange={handleOptionChange}
          minLength={minLength}
          maxLength={maxLength}
        />

        {/* Generate Button */}
        <GenerateButton
          onGenerate={handleGeneratePassword}
          disabled={!hasValidOptions(options)}
        />
      </div>
    </div>
  );
};

Step 2: Custom Hooks Implementation

// hooks/usePasswordGenerator.ts
import { useCallback, useMemo } from 'react';

export function usePasswordGenerator() {
  const charSets = useMemo(() => ({
    uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
    lowercase: 'abcdefghijklmnopqrstuvwxyz',
    numbers: '0123456789',
    symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?',
    similar: 'il1Lo0O',
    ambiguous: '{}[]()/\\\'"`~,;:.<>'
  }), []);

  const generatePassword = useCallback((options: PasswordOptions): string => {
    const selectedSets = Object.entries(options)
      .filter(([key, value]) => 
        key !== 'length' && 
        key !== 'excludeSimilar' && 
        key !== 'excludeAmbiguous' && 
        value
      )
      .map(([key]) => key as keyof typeof charSets);

    if (selectedSets.length === 0) {
      throw new Error('At least one character set must be selected');
    }

    let availableChars = selectedSets
      .map(set => charSets[set])
      .join('');

    // Remove similar characters if requested
    if (options.excludeSimilar) {
      availableChars = availableChars
        .split('')
        .filter(char => !charSets.similar.includes(char))
        .join('');
    }

    // Remove ambiguous characters if requested
    if (options.excludeAmbiguous) {
      availableChars = availableChars
        .split('')
        .filter(char => !charSets.ambiguous.includes(char))
        .join('');
    }

    // Ensure at least one character from each selected set
    let password = '';
    const minCharsPerSet = Math.floor(options.length / selectedSets.length);
    const remainingLength = options.length - (minCharsPerSet * selectedSets.length);

    selectedSets.forEach(set => {
      const setChars = charSets[set];
      for (let i = 0; i < minCharsPerSet; i++) {
        const randomIndex = Math.floor(Math.random() * setChars.length);
        password += setChars[randomIndex];
      }
    });

    // Fill remaining length with random characters
    for (let i = 0; i < remainingLength; i++) {
      const randomIndex = Math.floor(Math.random() * availableChars.length);
      password += availableChars[randomIndex];
    }

    // Shuffle the password
    return password
      .split('')
      .sort(() => Math.random() - 0.5)
      .join('');
  }, [charSets]);

  const calculateStrength = useCallback((password: string): PasswordStrength => {
    let score = 0;
    
    // Length contribution
    score += Math.min(password.length * 4, 40);
    
    // Character set diversity
    const hasUppercase = /[A-Z]/.test(password);
    const hasLowercase = /[a-z]/.test(password);
    const hasNumbers = /\d/.test(password);
    const hasSymbols = /[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(password);
    
    const uniqueSets = [hasUppercase, hasLowercase, hasNumbers, hasSymbols]
      .filter(Boolean).length;
    
    score += (uniqueSets - 1) * 10;
    
    // Entropy calculation
    const charsetSize = (hasUppercase ? 26 : 0) + 
                       (hasLowercase ? 26 : 0) + 
                       (hasNumbers ? 10 : 0) + 
                       (hasSymbols ? 32 : 0);
    
    const entropy = Math.log2(Math.pow(charsetSize, password.length));
    score += Math.min(entropy / 2, 30);

    // Determine strength level
    let label: string;
    let color: string;
    
    if (score < 30) {
      label = 'Very Weak';
      color = '#ff4444';
    } else if (score < 50) {
      label = 'Weak';
      color = '#ff8800';
    } else if (score < 70) {
      label = 'Medium';
      color = '#ffaa00';
    } else if (score < 90) {
      label = 'Strong';
      color = '#00aa00';
    } else {
      label = 'Very Strong';
      color = '#008800';
    }

    return { score, label, color };
  }, []);

  return {
    generatePassword,
    calculateStrength
  };
}

// hooks/useClipboard.ts
import { useState, useCallback } from 'react';

export function useClipboard() {
  const [isCopied, setIsCopied] = useState(false);

  const copyToClipboard = useCallback(async (text: string) => {
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(text);
      } else {
        // Fallback for older browsers
        const textArea = document.createElement('textarea');
        textArea.value = text;
        textArea.style.position = 'fixed';
        textArea.style.left = '-999999px';
        textArea.style.top = '-999999px';
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();
        document.execCommand('copy');
        textArea.remove();
      }
      
      setIsCopied(true);
      setTimeout(() => setIsCopied(false), 2000);
    } catch (error) {
      console.error('Failed to copy to clipboard:', error);
    }
  }, []);

  return { copyToClipboard, isCopied };
}

Step 3: Component Implementation

// components/PasswordDisplay.tsx
import React from 'react';

interface PasswordDisplayProps {
  password: string;
  onCopy: () => void;
  isCopied: boolean;
}

const PasswordDisplay: React.FC<PasswordDisplayProps> = ({
  password,
  onCopy,
  isCopied
}) => {
  return (
    <div className="password-display">
      <div className="password-field">
        <input
          type="text"
          value={password}
          readOnly
          placeholder="Generated password will appear here..."
          className="password-input"
          aria-label="Generated password"
        />
        {password && (
          <button
            onClick={onCopy}
            className={`copy-button ${isCopied ? 'copied' : ''}`}
            title={isCopied ? 'Copied!' : 'Copy to clipboard'}
            aria-label={isCopied ? 'Password copied' : 'Copy password to clipboard'}
          >
            {isCopied ? '✓' : '📋'}
          </button>
        )}
      </div>
    </div>
  );
};

// components/OptionsPanel.tsx
import React from 'react';

interface OptionsPanelProps {
  options: PasswordOptions;
  onOptionChange: (name: string, value: boolean | number) => void;
  minLength: number;
  maxLength: number;
}

const OptionsPanel: React.FC<OptionsPanelProps> = ({
  options,
  onOptionChange,
  minLength,
  maxLength
}) => {
  return (
    <div className="options-panel">
      <h3>Password Options</h3>
      
      {/* Length Slider */}
      <div className="option-group">
        <label htmlFor="length-slider">
          Password Length: {options.length}
        </label>
        <input
          id="length-slider"
          type="range"
          min={minLength}
          max={maxLength}
          value={options.length}
          onChange={(e) => onOptionChange('length', parseInt(e.target.value))}
          className="length-slider"
        />
        <div className="length-labels">
          <span>{minLength}</span>
          <span>{maxLength}</span>
        </div>
      </div>

      {/* Character Set Options */}
      <div className="option-group">
        <h4>Character Sets</h4>
        <div className="checkbox-group">
          <label className="checkbox-label">
            <input
              type="checkbox"
              checked={options.uppercase}
              onChange={(e) => onOptionChange('uppercase', e.target.checked)}
            />
            Uppercase Letters (A-Z)
          </label>
          
          <label className="checkbox-label">
            <input
              type="checkbox"
              checked={options.lowercase}
              onChange={(e) => onOptionChange('lowercase', e.target.checked)}
            />
            Lowercase Letters (a-z)
          </label>
          
          <label className="checkbox-label">
            <input
              type="checkbox"
              checked={options.numbers}
              onChange={(e) => onOptionChange('numbers', e.target.checked)}
            />
            Numbers (0-9)
          </label>
          
          <label className="checkbox-label">
            <input
              type="checkbox"
              checked={options.symbols}
              onChange={(e) => onOptionChange('symbols', e.target.checked)}
            />
            Symbols (!@#$%^&*)
          </label>
        </div>
      </div>

      {/* Advanced Options */}
      <div className="option-group">
        <h4>Advanced Options</h4>
        <div className="checkbox-group">
          <label className="checkbox-label">
            <input
              type="checkbox"
              checked={options.excludeSimilar}
              onChange={(e) => onOptionChange('excludeSimilar', e.target.checked)}
            />
            Exclude Similar Characters (i, l, 1, L, o, 0, O)
          </label>
          
          <label className="checkbox-label">
            <input
              type="checkbox"
              checked={options.excludeAmbiguous}
              onChange={(e) => onOptionChange('excludeAmbiguous', e.target.checked)}
            />
            Exclude Ambiguous Characters ({ } [ ] ( ) / \ ' " ` ~ , ; : . < >)
          </label>
        </div>
      </div>
    </div>
  );
};

// components/StrengthMeter.tsx
import React from 'react';

interface StrengthMeterProps {
  strength: PasswordStrength;
}

const StrengthMeter: React.FC<StrengthMeterProps> = ({ strength }) => {
  return (
    <div className="strength-meter">
      <div className="strength-header">
        <span>Password Strength:</span>
        <span className="strength-label" style={{ color: strength.color }}>
          {strength.label}
        </span>
      </div>
      
      <div className="strength-bar">
        <div 
          className="strength-fill"
          style={{ 
            width: `${strength.score}%`,
            backgroundColor: strength.color
          }}
        />
      </div>
      
      <div className="strength-score">
        Score: {strength.score}/100
      </div>
    </div>
  );
};

// components/GenerateButton.tsx
import React from 'react';

interface GenerateButtonProps {
  onGenerate: () => void;
  disabled: boolean;
}

const GenerateButton: React.FC<GenerateButtonProps> = ({
  onGenerate,
  disabled
}) => {
  return (
    <button
      onClick={onGenerate}
      disabled={disabled}
      className={`generate-button ${disabled ? 'disabled' : ''}`}
      aria-label="Generate new password"
    >
      🔄 Generate Password
    </button>
  );
};

UI/UX Enhancements

Modern CSS Styling

/* components/PasswordGenerator.css */
.password-generator {
  max-width: 600px;
  margin: 0 auto;
  padding: 2rem;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 20px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
  color: white;
}

.password-generator-header {
  text-align: center;
  margin-bottom: 2rem;
}

.password-generator-header h1 {
  font-size: 2.5rem;
  margin-bottom: 0.5rem;
  font-weight: 700;
}

.password-generator-header p {
  font-size: 1.1rem;
  opacity: 0.9;
}

.password-display {
  margin-bottom: 2rem;
}

.password-field {
  position: relative;
  display: flex;
  align-items: center;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  padding: 0.5rem;
  backdrop-filter: blur(10px);
}

.password-input {
  flex: 1;
  background: transparent;
  border: none;
  color: white;
  font-size: 1.2rem;
  font-family: 'Courier New', monospace;
  padding: 1rem;
  outline: none;
}

.password-input::placeholder {
  color: rgba(255, 255, 255, 0.6);
}

.copy-button {
  background: rgba(255, 255, 255, 0.2);
  border: none;
  border-radius: 8px;
  padding: 0.75rem;
  color: white;
  cursor: pointer;
  transition: all 0.3s ease;
  margin-left: 0.5rem;
}

.copy-button:hover {
  background: rgba(255, 255, 255, 0.3);
  transform: translateY(-2px);
}

.copy-button.copied {
  background: #10b981;
  animation: pulse 0.6s ease-in-out;
}

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.1); }
}

.strength-meter {
  margin-bottom: 2rem;
  padding: 1.5rem;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  backdrop-filter: blur(10px);
}

.strength-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.strength-label {
  font-weight: 600;
  font-size: 1.1rem;
}

.strength-bar {
  height: 8px;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 0.5rem;
}

.strength-fill {
  height: 100%;
  transition: width 0.3s ease;
}

.options-panel {
  margin-bottom: 2rem;
  padding: 1.5rem;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  backdrop-filter: blur(10px);
}

.option-group {
  margin-bottom: 1.5rem;
}

.option-group h4 {
  margin-bottom: 1rem;
  font-size: 1.1rem;
  font-weight: 600;
}

.length-slider {
  width: 100%;
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.2);
  outline: none;
  margin: 1rem 0;
}

.length-slider::-webkit-slider-thumb {
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: white;
  cursor: pointer;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}

.checkbox-group {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
}

.checkbox-label {
  display: flex;
  align-items: center;
  cursor: pointer;
  padding: 0.75rem;
  border-radius: 8px;
  transition: background-color 0.3s ease;
}

.checkbox-label:hover {
  background: rgba(255, 255, 255, 0.1);
}

.checkbox-label input[type="checkbox"] {
  margin-right: 0.75rem;
  width: 18px;
  height: 18px;
  accent-color: #10b981;
}

.generate-button {
  width: 100%;
  padding: 1rem 2rem;
  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  color: white;
  border: none;
  border-radius: 12px;
  font-size: 1.2rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
}

.generate-button:hover:not(.disabled) {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
}

.generate-button.disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

/* Responsive Design */
@media (max-width: 768px) {
  .password-generator {
    padding: 1rem;
    margin: 1rem;
  }
  
  .password-generator-header h1 {
    font-size: 2rem;
  }
  
  .checkbox-group {
    grid-template-columns: 1fr;
  }
}

Performance Optimizations

Security Enhancements

// Enhanced security with crypto API
const generateSecurePassword = (options: PasswordOptions): string => {
  const array = new Uint8Array(options.length);
  crypto.getRandomValues(array);
  
  // Convert to secure password using selected character sets
  // ... implementation
};

// Password validation
const validatePassword = (password: string): boolean => {
  const minLength = 8;
  const hasUppercase = /[A-Z]/.test(password);
  const hasLowercase = /[a-z]/.test(password);
  const hasNumbers = /\d/.test(password);
  const hasSymbols = /[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(password);
  
  return password.length >= minLength && 
         (hasUppercase || hasLowercase) && 
         (hasNumbers || hasSymbols);
};

Key Takeaways

What You've Learned

  1. Security Best Practices: Cryptographically secure random generation
  2. User Experience: Intuitive interface with immediate feedback
  3. Accessibility: ARIA labels and keyboard navigation
  4. Performance: Optimized rendering and state management
  5. Modern APIs: Clipboard API and crypto API usage

Common Pitfalls to Avoid

  • Weak Randomness: Don't use Math.random() for security
  • Poor UX: Missing loading states and error handling
  • Accessibility Issues: Missing ARIA labels and keyboard support
  • Insecure Storage: Don't store passwords in localStorage
  • Poor Validation: Missing input validation and edge cases

Next Steps

  • Implement password strength visualization
  • Add password export functionality
  • Create password history management
  • Add biometric authentication support
  • Implement password sharing features

This password generator component demonstrates advanced React patterns, security best practices, and modern web APIs. The skills you've learned here are essential for building secure, user-friendly applications that handle sensitive data properly! 🔐