Real-time password verification in React

Note: the code for this tutorial can be found here.

Password verification is one of the most annoying things in web development, both for developers and users. Nonetheless, weak passwords are the single most common attack vector for cyberattacks, so it's imperative that we take it seriously and enforce strong password rules at the time of creation.

From a user perspective, trying to create a strong password has a lot of cognitive overhead. "You're telling me it has to have an uppercase AND lowercase AND number and special characters AND at least 10 characters?! How am I supposed to remember that?!"

With a little clever coding, you can lighten the cognitive load and show the user in real time whether the password is strong enough. The example above shows how you can validate a password in real time and provide visual feedback to the user.

The Rules

As the example shows, we have a total of five rules to enforce:

  • At least 10 characters

  • At least one each of uppercase, lowercase and numeral

  • At least one of the following special characters: @#$%!^&*

For the first rule, we will use a simple length check. The rest will use regular expressions.

Here is a function that returns each of these checks plus the overall results as an object with properties for each validation. It also returns a boolean property called valid that is based on whether all of the validations pass.

export const validPassword = (password: string) => {
  const length: boolean = password.length > 9;
  const caps: boolean = /[A-Z]/.test(password)
  const lower: boolean = /[a-z]/.test(password)
  const numeral: boolean = /[0-9]/.test(password)
  const special: boolean = /[@#$%!^&*]/.test(password)
  const valid: boolean = length && caps && lower && special && numeral  
  return {length, caps, lower, numeral, special, valid}
}
//example
console.log(validPassword("password"))
//outputs
{
  length: false
  caps: false
  lower: true
  numeral: false
  special: false
  valid: false
}

Now that we have a single source of truth for all the rules, we can apply them to the rules text in a component called PasswordDescription. In this case we are using Tailwind CSS to apply styling that changes the text color to red or green based on the results. It also only triggers if the password is not an empty string. That way on initial form load the description is neutral.

type PasswordDescriptionProps = {
  password: string | undefined
}
export const PasswordDescription = ({password = ''}: PasswordDescriptionProps) => {
  const {valid, length, caps, lower, numeral, special} = validPassword(password)
  return (
        <ul className={`list-disc ${password && !valid ? "text-red-500" : ""}`}>
          <li className={password && length ? "text-green-400" : ""}>at least 10 characters</li>
          <li className={password && lower ? "text-green-400" : ""}>at least 1 lowercase letter</li>
          <li className={password && caps ? "text-green-400" : ""}>at least 1 uppercase letter</li>
          <li className={password && numeral ? "text-green-400" : ""}>at least 1 numeral</li>
          <li className={password && special ? "text-green-400" : ""}>at least 1 special character (@#$%!^&*)</li>
        </ul>
    )
}

The opening <ul> sets child elements to to red if the overall password is invalid. Each <li></li> row changes to green if the individual rule passes.

Putting it All Together

Here's how to use this in a larger component that has the actual password fields. I've added very minimal styling so it's easier to copy and paste. I also added a helper function called passwordsMatch() that returns boolean based on whether the password and repeat password fields match (I named the second field passwordCheck).

import { PasswordDescription, validPassword } from '.passwordValidator'
import { useState } from 'React'
export function PasswordFields(): JSX.Element {

const [password, setPassword] = useState('')
const [passwordCheck, setPasswordCheck] = useState('')
//helper functions to check whether the password passes all checks
const isValid = () => validPassword(password).valid; 
const passwordsMatch = () => password === passwordCheck; 
return (
  <fieldset>
    <legend>Password</legend>
    <PasswordDescription password={password} />}
    <div>
      <label htmlFor="password">Password</label>
      <div>
        <input
          name="password"
          type="password"
          id="password"
          className={isValid() ?  "border border-black" : "border border-red-500"}
          onChange={(e)=>setPassword(e.target.value)}
        />
      </div>
    </div>
    <div>
      <label htmlFor="password_check">Repeat password</label>
      <div>
        <input
          name="passwordCheck"
          type="password"
          id="passwordCheck"
          className={passwordsMatch() ?  "border border-black" : "border border-red-500"}
          onChange={(e)=>setPasswordCheck(e.target.value)}
         />
      </div>
    </div>
  </fieldset>
  )
}

And that's it! What do you think? Let me know in the comments.