Password generators are a common sight on the web these days. Websites often won’t allow you to create an account without a secure enough password. In this tutorial, we’ll build a random password generator with JavaScript that can be configured on the fly.
Our JavaScript Password Generator
Here’s a preview of the tool we’ll be building in this tutorial. The three settings allow you to:
- Define the password length
- Define whether or not numbers will be included in the result
- Define whether or not symbols will be included in the result
Click on Generate to get a random password, then click Copy to copy it to your clipboard!
This is an intermediate level tutorial, although it’s explained in such a way that even beginners can follow along. The best way to learn is to do!
The Password Tool’s UI
Let’s begin by building a UI with HTML and CSS, then we’ll address the JavaScript once that’s done.
1. The HTML
Using some basic HTML and CSS, I’ll start by adding a set of controls and CSS styles the generator will use. Below is the main HTML markup.
<div class="container"> <h2>Password Generator</h2> <div class="result-container"> <input type="text" id="result" /> <button class="copy-result" id="copy">Copy</button> </div> <div class="settings"> <div class="input-group"> <label>Password length (4-20)</label> <input type="range" id="length" min="4" max="20" step="1" /> <span id="length-result">10</span> </div> <div class="input-group"> <label>Include numbers</label> <input type="checkbox" id="numbers" checked /> </div> <div class="input-group"> <label>Include symbols</label> <input type="checkbox" id="symbols" checked /> </div> </div> <button class="generate-btn" id="generate">Generate</button> </div>
The generator has a text input where the final password will display and a set of controls to configure the password character types, including numbers, symbols, and length. There’s a Generate button and a Copy button to make it easy to grab the password for quick use.
For the body of the page I’ll use a Google font called Lexend. I added the following code within the head
tags of the HTML page so the font renders.
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Lexend:wght@400;600;700;800&display=swap" rel="stylesheet">
2. The CSS
The CSS I added is as follows.
:root { --teal: #06b6d4; --blue: #3b82f6; --lightgray: #cbd5e1; --gradient: linear-gradient(to right, var(--teal), var(--blue)); } body { background-image: var(--gradient); font-family: "Lexend", sans-serif; accent-color: var(--blue); -webkit-font-smoothing: antialiased; } .container { max-width: 390px; margin: 5rem auto; padding: 26px; border-radius: 20px; background: white; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); } .result-container { display: flex; align-items: center; justify-content: space-between; position: relative; } #result { flex: 1; font-family: Monaco, mono; background: #f4f4f4; } .copy-result { background-image: var(--gradient); border: none; padding: 14px 18px; color: white; border-radius: 25px; margin-left: 16px; position: absolute; right: 12px; font-weight: 600; cursor: pointer; z-index: 50; font-size: 0.8rem; } .copy-result:hover { background: var(--blue); } input[type="text"] { padding: 20px 24px; border: 1px solid var(--lightgray); border-radius: 50px; } input[type="range"] { padding: 8px 10px; background: #f8f8f8; flex: 1; margin-left: 1rem; margin-right: 1rem; } input[type="text"]:focus, input[type="number"]:focus { border: 1px solid var(--teal); outline: none; } input[type="checkbox"] { width: 16px; height: 16px; } .settings { margin-top: 3rem; } .input-group { margin-bottom: 2rem; display: flex; align-items: center; justify-content: space-between; } .generate-btn { background-image: var(--gradient); padding: 14px 24px; border: none; font-weight: 600; color: white; display: block; width: 100%; border-radius: 25px; cursor: pointer; font-size: 1.25rem; } .generate-btn:hover, .copy-btn:hover { box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); background: var(--blue); } .alert { position: fixed; top: 6px; right: 6px; padding: 10px; border-radius: 4px; background: rgba(0, 0, 0, 0.4); color: white; font-size: 20px; }
You’ll see I’ve added a gradient for the main background. I also used newer CSS variables to avoid repeating myself in a few spots concerning colors.
So far we’ve built a static HTML and CSS UI template:
Adding Behavior With JavaScript
Generating random characters, strings, and numbers in JavaScript can be accomplished using built-in features of the JavaScript API. These are confusing at first glance, but with enough practice, you can become more comfortable with them in no time.
1. Target HTML Elements
I’ll start by targeting all the necessary HTML elements using unique identifiers. Those will live at the top of my JavaScript file.
const result = document.querySelector("#result"); const passLength = document.querySelector("#length"); const passLengthResult = document.querySelector("#length-result"); const includeNumbers = document.querySelector("#numbers"); const includeSymbols = document.querySelector("#symbols"); const generateBtn = document.querySelector("#generate"); const copyPass = document.querySelector("#copy");
Refer to the HTML with the ID of what we pass for each constant variable above. We’ll need to target all the form elements and controls, and there are quite a few!
We can begin by setting up the password length field with the variables in place. This is an HTML range input. We can manipulate it with JavaScript and display the current setting as it updates.
// Listen for password range change passLength.addEventListener("change", (event) => { passLengthResult.innerText = event.target.value; });
If the range slider is changed, I want to update the number displayed on the page to reflect it. This number is purely a visual enhancement, so the end user knows firsthand the number of characters they are configuring their password.
2. Randomizing Functions
Next up, we’ll add a set of functions we can call that will return randomized values based on what gets configured.
function getRandomLower() { return String.fromCharCode(Math.floor(Math.random() * 26) + 97); } function getRandomNumber() { return String.fromCharCode(Math.floor(Math.random() * 10) + 48); } function getRandomSymbol() { const symbols = "!@#$%^&*(){}[]=<>/,."; return symbols[Math.floor(Math.random() * symbols.length)]; }
Let me start by saying I know these are very confusing to understand! Each function returns a string value. The built-in String.fromCharCode
method can create a new string from an existing character code. This means a string character can symbolize a number added.
To make more sense of this, you can open your browser’s console tab and paste the return
line from one of these functions. Change the number at the end, and you should see new characters appear each time it runs.
Using JavaScript’s Math.floor()
and Math.random()
we can be sure we return unique characters each time.
3. Iterate Over Password Length, Adding Generated Characters
Let’s now iterate over a loop in JavaScript, based on the password length configured, and use our randomly generated characters.
generateBtn.addEventListener("click", () => { const length = passLength.value; const numbers = includeNumbers.checked; const symbols = includeSymbols.checked; result.value = generatePassword(numbers, symbols, length); }); function generatePassword(number, symbol, length) { let generatedPassword = ""; let variationsCount = [number, symbol].length; for (let i = 0; i < length; i += variationsCount) { if (number) { generatedPassword += getRandomNumber(); } if (symbol) { generatedPassword += getRandomSymbol(); } generatedPassword += getRandomLower(); } const finalPassword = generatedPassword.slice(0, length); return finalPassword; }
The code above is triggered when the Generate button gets clicked. We get the values of the fields we’ve already queried for and pass those to a new function called generatePassword
. This function returns a unique password we assign to the final result input for use.
result.value = generatePassword(numbers, symbols, length);
The generatePassword
function accepts three parameters, number
, symbol
, and length
, which map to the controls on our generator form.
This part gets confusing, but inside the function, we use a for loop that cycles through the length the password gets configured to be. So if the password were twenty characters, it would loop twenty times.
During each iteration, we can check if the checkbox values for the number or symbol options are checked. If they are checked, we add to and assign a variable called generatedPassword
that initially had no value.
This new value can be assigned and reassigned based on the options set and the current iteration of the loop.
By default, we’ll always include lowercase letters in the password generator so that line comes last without any conditional statement.
Finally, we’ll slice() the password at the length that it gets configured for and return the newly generated password..
Still with me? The hard part is over, I promise.
4. Click to Copy (User Experience)
What’s a password generator without a handy way to copy the generated password? To enhance our tool, we can use JavaScript to tap into some internal APIs. This will dynamically copy the text from the result input.
// Listen for copy button copyPass.addEventListener("click", () => { copy(result.value); }); // Copy generated password in more secure way function copy(text) { const input = document.createElement("input"); input.setAttribute("value", text); document.body.appendChild(input); input.select(); let copiedResult = document.execCommand("copy"); document.body.removeChild(input); const alert = document.createElement("div"); alert.classList.add("alert"); alert.textContent = "Copied!"; document.body.appendChild(alert); setTimeout(() => { document.querySelector(".alert").style.display = "none"; document.body.removeChild(alert); }, 1000); return result; }
To correctly copy the password with the click of a button, we’ll need to create a new input field, select the area dynamically, copy the password into it and then quickly remove it from the page to make this all work well.
You might think this seems complex and clunky, and I’d 100% agree with you, but it works pretty well and is pretty secure.
To piggyback on this code, I’ve added a small alert piece of UI we can dynamically add and remove after a short period. This is all done by creating HTML with JavaScript and removing it quickly from view after the “copying” action is complete.
If you recall, I added a .alert
class with some styles back to our CSS. Here is where that all comes into play.
5. Generate a Password on the First Page Load
Until now, you’ve needed to click the Generate button to get a generated password to appear. I think it would be nice if one appeared as soon as you loaded the page. We can reuse the generatePassword
function and set a few new variables to make this a reality, and I’ll also set a default password length on page load.
// Set default password length 20 max on load document.addEventListener("DOMContentLoaded", () => { passLength.value = 20; passLengthResult.innerText = "20"; let onLoadLength = passLength.value; let onLoadNumbers = includeNumbers.checked; let onLoadSymbols = includeSymbols.checked; result.value = generatePassword(onLoadNumbers, onLoadSymbols, onLoadLength); });
6. Tying it All Together!
Here’s all the JavaScript in its final form. There is a decent amount of code here, but I hope it isn’t too daunting. This password generator could be great for a sign-up form to help convince users to opt-in for a stronger password.
const result = document.querySelector("#result"); const passLength = document.querySelector("#length"); const passLengthResult = document.querySelector("#length-result"); const includeNumbers = document.querySelector("#numbers"); const includeSymbols = document.querySelector("#symbols"); const generateBtn = document.querySelector("#generate"); const copyPass = document.querySelector("#copy"); // Set default password length 20 max on load document.addEventListener("DOMContentLoaded", () => { passLength.value = 20; passLengthResult.innerText = 20; let onLoadLength = passLength.value; let onLoadNumbers = includeNumbers.checked; let onLoadSymbols = includeSymbols.checked; result.value = generatePassword(onLoadNumbers, onLoadSymbols, onLoadLength); }); // Listen for password range change passLength.addEventListener("change", (event) => { passLengthResult.innerText = event.target.value; }); // Listen for copy button copyPass.addEventListener("click", () => { copy(result.value); }); generateBtn.addEventListener("click", () => { const length = passLength.value; const numbers = includeNumbers.checked; const symbols = includeSymbols.checked; result.value = generatePassword(numbers, symbols, length); }); function generatePassword(number, symbol, length) { let generatedPassword = ""; let variationsCount = [number, symbol].length; for (let i = 0; i < length; i += variationsCount) { if (number) { generatedPassword += getRandomNumber(); } if (symbol) { generatedPassword += getRandomSymbol(); } generatedPassword += getRandomLower(); } const finalPassword = generatedPassword.slice(0, length); return finalPassword; } function getRandomLower() { return String.fromCharCode(Math.floor(Math.random() * 26) + 97); } function getRandomNumber() { return String.fromCharCode(Math.floor(Math.random() * 10) + 48); } function getRandomSymbol() { const symbols = "!@#$%^&*(){}[]=<>/,."; return symbols[Math.floor(Math.random() * symbols.length)]; } // Copy generated password in more secure way function copy(text) { const input = document.createElement("input"); input.setAttribute("value", text); document.body.appendChild(input); input.select(); let copiedResult = document.execCommand("copy"); document.body.removeChild(input); const alert = document.createElement("div"); alert.classList.add("alert"); alert.textContent = "Copied!"; document.body.appendChild(alert); setTimeout(() => { document.querySelector(".alert").style.display = "none"; document.body.removeChild(alert); }, 1000); return result; }
Conclusion
And we’re done! Let’s remind ourselves what we’ve built:
This code has a considerable amount of complexity, so don’t feel you need to understand it all if you are a JavaScript novice. However I hope this tutorial helped you make sense of some advanced use cases for JavaScript, and empowers you to keep building.
If you get confused, take a step back and search the web for some built-in methods like Math.floor()
and the approaches seen here. With enough practice and repetition, using these built-in methods will come naturally!
No comments:
Post a Comment