Password generation is something you’re hopefully using a password manager for these days. However, you might not be aware that modern browsers support some great crypto features.
In this quick little tutorial, we will use the Crypto API to create a strong password generator. The code is remarkably simple, and you can adapt this to generate unique values for games and other purposes besides passwords.
/** * Generates a random password of the specified length. * * @param length The length of the password to generate. * @returns A Promise that resolves to a string containing the generated password. * @throws An error if the length argument is less than 1. */ const generatePassword = async (length: number): Promise<string> => { if (length < 1) { throw new Error('Length must be greater than 0'); } // Create a new Uint8Array with the specified length. const buffer = new Uint8Array(length); // Get the browser's crypto object for generating random numbers. const crypto = window.crypto || (window as any).msCrypto; // For compatibility with IE11. // Generate random values and store them in the buffer. const array = await crypto.getRandomValues(buffer); // Initialize an empty string to hold the generated password. let password = ''; // Define the characters that can be used in the password. const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; // Iterate over the array of random values and add characters to the password. for (let i = 0; i < length; i++) { // Use the modulus operator to get a random index in the characters string // and add the corresponding character to the password. password += characters.charAt(array[i] % characters.length); } // Return the generated password. return password; };
As you can see, the getRandomValues method does all of the heavy lifting here for generating our password. We also define what characters are allowed in our password, allowing us to remove ambiguous characters if we wish.
Testing
We will write some simple Jest unit tests to ensure our code works. The basics, such as password length and uniqueness.
describe('generatePassword', () => { test('should return a string of the specified length', async () => { const password = await generatePassword(10); expect(typeof password).toBe('string'); expect(password.length).toBe(10); }); test('should throw an error if the length argument is less than 1', async () => { await expect(generatePassword(0)).rejects.toThrow(); await expect(generatePassword(-1)).rejects.toThrow(); }); test('should generate different passwords for different calls', async () => { const password1 = await generatePassword(10); const password2 = await generatePassword(10); expect(password1).not.toBe(password2); }); test('should only contain characters from the defined set', async () => { const password = await generatePassword(10); expect(password).toMatch(/^[A-Za-z0-9]+$/); }); });
Nice code. I probably would have written this as a web tier service. The one thing missing is some kind of loop/filter for vulgarities in the generated password (which is why I’d prefer this code on the web tier, so the blacklist is not visible).