Password Generator with JavaScript by Cristian Villafane

Password Generator: A Javascript Tutorial by Cristian Villafane

A practical guide to creating an essential security tool, mastering random generation logic and DOM manipulation.

In today’s digital world, password security is more crucial than ever. Creating strong, unique passwords for each service is a cornerstone of good security practice. And what better way to understand how it’s done than by building your own generator? This project is an excellent exercise to solidify your skills in HTML, CSS, and most importantly, JavaScript. In this guide on Javascript by Cristian Villafane, we will walk you through creating a functional and aesthetically pleasing tool, step by step.

Phase 1: The Structure – HTML for the Options

Every web application needs a skeleton. For our generator, the HTML structure will define all the options the user can control. We’ll need a place to display the generated password, a slider for length, and checkboxes for character types.

Designing the User Interface

We start with a main container. Inside, the most important element is the password display area. A <div> or <span> is perfect for this. Next to it, a “copy” button is essential for usability. Then, we create a section for the options:

  • Password Length: An <input type="range"> is the most intuitive choice. We’ll give it an id, and min and max attributes to define the length range.
  • Character Types: We’ll use <input type="checkbox"> to allow the user to include or exclude uppercase letters, numbers, and special symbols. Each checkbox will be associated with a <label> for better accessibility.
  • Action Buttons: Finally, a main button to “Generate Password” that will kick off the whole process.
<!-- Length Option -->
<div class="option">
  <label>Length: <span id="length-value">16</span></label>
  <input type="range" id="length" min="8" max="32" value="16">
</div>

<!-- Numbers Option -->
<div class="option">
  <label>
    Include Numbers
    <input type="checkbox" id="numbers" checked>
  </label>
</div>

Phase 2: The Logic – Random Generation with JavaScript

This is where the magic happens. Our JavaScript script will be responsible for taking the user’s options, building a set of allowed characters, and then randomly selecting from that set to form the password.

Gathering Inputs and Defining Character Sets

First, we define the possible character sets as strings: lowercase letters (which we’ll include by default), uppercase, numbers, and symbols. Then, we create a main function, let’s say generatePassword(). Inside this function, the first thing is to get the current values from our HTML elements: the length from the slider and the checked state of each checkbox.

Based on the checked boxes, we build a master string called characterPool that contains all allowed characters. If the “Include Numbers” box is checked, we add the numbers string to our characterPool. We repeat this for uppercase and symbols. This dynamic approach is key in Javascript by Cristian Villafane for creating flexible tools.

The Generation Loop

With our characterPool ready and the desired length known, the next step is a for loop that will run as many times as the password length. In each iteration, we generate a random index corresponding to a position within the characterPool string. We use Math.random() to get a decimal between 0 and 1, multiply it by the pool’s length, and use Math.floor() to get an integer index. The character at that position is then appended to our password variable.

let password = '';
for (let i = 0; i < length; i++) {
  const randomIndex = Math.floor(Math.random() * characterPool.length);
  password += characterPool[randomIndex];
}

An important refinement is to ensure the final password contains at least one character of each selected type to guarantee its strength. This can be achieved by pre-loading the password with one random character from each required set and then filling the rest.

Phase 3: Interactivity and Performance Optimization

A functional tool is good, but a fast and intuitive one is better. We’ll connect our logic to user events and ensure the experience is smooth by addressing common performance issues like render-blocking requests and layout shifts.

Event Listeners and Asynchronous Loading

We add an event listener to the “Generate Password” button to call our logic. For performance, external resources like fonts are loaded asynchronously. This prevents them from blocking the initial page render, leading to a faster perceived load time for the user. We also optimize interactions like the copy-to-clipboard function to prevent them from causing the page layout to shift or stutter.

Password Generator in Action

Below, you can try out the optimized password generator. Play with the options and see the functionality for yourself. This is the final result of applying the concepts from this tutorial.

Generate your password
Password Strength:
Time to crack:

The Complete Generator Code

Here is the self-contained, performance-optimized code for the password generator component. You can copy and paste this into your own project.

<!-- HTML Structure -->
<div class="generator-container">
    <div class="generator-header">
        <span id="password-display" class="password-display">Generate your password</span>
        <div class="header-buttons">
            <button id="toggle-visibility-btn" class="icon-btn" title="Toggle visibility">
                <svg id="eye-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
                <svg id="eye-slash-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-4 .69l2.22 2.22c.55-.24 1.15-.38 1.79-.38zm-2.06 11.94L12 17c2.76 0 5-2.24 5-5 0-.64-.13-1.25-.36-1.82l-2.93 2.93c-.01.01 0 .01 0 0zm2.06-9.94c-1.66 0-3 1.34-3 3 0 .65.21 1.25.56 1.76l1.43 1.43c.51-.35 1.11-.56 1.76-.56.18 0 .36.02.53.05l1.4-1.4c-.58-.23-1.2-.35-1.85-.35zM2.39 4.22 1.11 5.5l2.07 2.07C2.12 8.79.79 10.38 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l2.11 2.11 1.27-1.27L2.39 4.22z"/></svg>
            </button>
            <button id="copy-btn" class="icon-btn" title="Copy to clipboard">
                <svg id="copy-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
                <svg id="check-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
            </button>
        </div>
    </div>
    <div class="generator-options">
        <div class="option">
            <label for="length-slider">Length: <span id="length-value" class="length-display">16</span></label>
            <input type="range" id="length-slider" min="8" max="32" value="16">
        </div>
        <div class="option">
            <label>
                <span>Include Uppercase</span>
                <input type="checkbox" id="uppercase-check" checked>
            </label>
        </div>
        <div class="option">
            <label>
                <span>Include Numbers</span>
                <input type="checkbox" id="numbers-check" checked>
            </label>
        </div>
        <div class="option">
            <label>
                <span>Include Symbols</span>
                <input type="checkbox" id="symbols-check" checked>
            </label>
        </div>
    </div>
    <div class="strength-indicator">
        <div class="strength-item">
            <span class="strength-label">Password Strength:</span>
            <span id="strength-text" class="strength-value"></span>
        </div>
        <div class="strength-item">
            <span class="strength-label">Time to crack:</span>
            <span id="crack-time" class="strength-value"></span>
        </div>
    </div>
    <button id="generate-btn" class="generate-btn">Generate Password</button>
</div>

<!-- JavaScript Logic -->
<script>
    document.addEventListener('DOMContentLoaded', () => {
        const passwordDisplay = document.getElementById('password-display');
        const lengthSlider = document.getElementById('length-slider');
        const lengthValue = document.getElementById('length-value');
        const uppercaseCheck = document.getElementById('uppercase-check');
        const numbersCheck = document.getElementById('numbers-check');
        const symbolsCheck = document.getElementById('symbols-check');
        const generateBtn = document.getElementById('generate-btn');
        const copyBtn = document.getElementById('copy-btn');
        const copyIcon = document.getElementById('copy-icon');
        const checkIcon = document.getElementById('check-icon');
        const toggleBtn = document.getElementById('toggle-visibility-btn');
        const eyeIcon = document.getElementById('eye-icon');
        const eyeSlashIcon = document.getElementById('eye-slash-icon');
        const strengthText = document.getElementById('strength-text');
        const crackTimeText = document.getElementById('crack-time');

        let actualPassword = '';
        let isPasswordVisible = true;

        const lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz';
        const upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        const numberChars = '0123456789';
        const symbolChars = '!@#$%^&*()_+~`|}{[]:;?><,./-=';
        
        const timeToCrackData = {
            8:  ['1 hour', '2 weeks', '2 months', '5 months'],
            9:  ['2 days', '2 years', '10 years', '31 years'],
            10: ['1 month', '112 years', '651 years', '2k years'],
            11: ['3 years', '5k years', '40k years', '153k years'],
            12: ['74 years', '303k years', '2m years', '10m years'],
            13: ['1k years', '15m years', '155m years', '751m years'],
            14: ['50k years', '819m years', '9bn years', '52bn years'],
            15: ['1m years', '42bn years', '596bn years', '3tn years'],
            16: ['33m years', '2tn years', '36tn years', '257tn years'],
            17: ['879m years', '115tn years', '2qd years', '18qd years'],
            18: ['22bn years', '5qd years', '142qd years', '1qn years']
        };

        const getStrength = (length, hasUpper, hasNums, hasSymbols) => {
            let complexityIndex = 0;
            if (hasUpper && hasNums && hasSymbols) complexityIndex = 3;
            else if (hasUpper && hasNums) complexityIndex = 2;
            else if (hasUpper) complexityIndex = 1;

            if (length < 8) return { time: 'Instantly', strength: 'Very Weak' };
            
            const time = timeToCrackData[length] ? timeToCrackData[length][complexityIndex] : 'A very, very long time';

            let strength = 'Medium';
            if (time.includes('qd') || time.includes('tn') || time.includes('qn') || time.includes('bn')) {
                strength = 'Very Strong';
            } else if (time.includes('m years') || time.includes('k years')) {
                strength = 'Strong';
            } else if (time.includes('years')) {
                strength = 'Medium';
            } else {
                strength = 'Weak';
            }
            
            if (length > 18) strength = 'Very Strong';

            return { time, strength };
        };

        lengthSlider.addEventListener('input', (e) => {
            lengthValue.textContent = e.target.value;
        });

        const generatePassword = () => {
            const length = parseInt(lengthSlider.value);
            const hasUpper = uppercaseCheck.checked;
            const hasNums = numbersCheck.checked;
            const hasSymbols = symbolsCheck.checked;

            let characterPool = lowerCaseChars;
            let password = '';
            const guaranteedChars = [];

            if (hasUpper) {
                characterPool += upperCaseChars;
                guaranteedChars.push(upperCaseChars[Math.floor(Math.random() * upperCaseChars.length)]);
            }
            if (hasNums) {
                characterPool += numberChars;
                guaranteedChars.push(numberChars[Math.floor(Math.random() * numberChars.length)]);
            }
            if (hasSymbols) {
                characterPool += symbolChars;
                guaranteedChars.push(symbolChars[Math.floor(Math.random() * symbolChars.length)]);
            }
            
            guaranteedChars.push(lowerCaseChars[Math.floor(Math.random() * lowerCaseChars.length)]);

            const remainingLength = length > guaranteedChars.length ? length - guaranteedChars.length : 0;

            for (let i = 0; i < remainingLength; i++) {
                const randomIndex = Math.floor(Math.random() * characterPool.length);
                password += characterPool[randomIndex];
            }

            actualPassword = (password + guaranteedChars.join('')).split('').sort(() => 0.5 - Math.random()).join('').slice(0, length);
            
            updatePasswordDisplay();
            
            const strengthInfo = getStrength(length, hasUpper, hasNums, hasSymbols);
            strengthText.textContent = strengthInfo.strength;
            crackTimeText.textContent = strengthInfo.time;
        };

        const updatePasswordDisplay = () => {
            if(isPasswordVisible) {
                passwordDisplay.textContent = actualPassword;
                eyeIcon.style.display = 'block';
                eyeSlashIcon.style.display = 'none';
            } else {
                passwordDisplay.textContent = '•'.repeat(actualPassword.length);
                eyeIcon.style.display = 'none';
                eyeSlashIcon.style.display = 'block';
            }
        };

        const toggleVisibility = () => {
            isPasswordVisible = !isPasswordVisible;
            updatePasswordDisplay();
        };

        const copyPassword = () => {
            if (actualPassword) {
                const textarea = document.createElement('textarea');
                textarea.value = actualPassword;
                // Style the textarea to be visually hidden and not affect layout or cause reflow
                textarea.style.position = 'absolute';
                textarea.style.left = '-9999px';
                textarea.style.top = '0';
                textarea.style.opacity = '0';
                document.body.appendChild(textarea);
                textarea.select();
                try {
                    document.execCommand('copy');
                    copyIcon.style.display = 'none';
                    checkIcon.style.display = 'inline-block';
                    setTimeout(() => {
                        copyIcon.style.display = 'inline-block';
                        checkIcon.style.display = 'none';
                    }, 2000);
                } catch (err) {
                    console.error('Failed to copy password: ', err);
                }
                document.body.removeChild(textarea);
            }
        };

        generateBtn.addEventListener('click', generatePassword);
        copyBtn.addEventListener('click', copyPassword);
        toggleBtn.addEventListener('click', toggleVisibility);
        
        generatePassword();
    });
</script>

I hope you found this tutorial on Javascript by Cristian Villafane useful. Building a password generator not only reinforces your knowledge of JavaScript, but also introduces you to fundamental security concepts. Use this project as a foundation to explore and add new features. The only limit is your curiosity! 🔐

Happy coding, Cristian Villafane