skip to Main Content

I’m trying to validate a password using the following regex in JavaScript:

/^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]+$/

The validation requirements are:

  • At least one lowercase letter
  • At least one uppercase letter
  • At least one number
  • At least one special character

Here is the password validation configuration I use:

password: {
  type: "string",
  required: "Password is required",
  matches: {
    regex:
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]+$/,
    message:
      "Please include at least one uppercase letter, one lowercase letter, one number, and one special character.",
  },
  min: {
    value: 8,
    message: "Password must be at least 8 characters",
  },
  max: {
    value: 20,
    message: "Password must not exceed 20 characters",
  },
}

However, this regex fails for the string ogJ((48HzpZI.

The string meets all the criteria:

  • Contains a lowercase letter (o, g, p, z, i)
  • Contains an uppercase letter (J, H, Z, I)
  • Contains a digit (4, 8)
  • Contains special characters ((, ()

Can anyone help identify why the regex doesn’t pass this string?

I tried validating the password string ogJ((48HzpZI using my regex:

/^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]+$/

I expected the validation to pass because the string meets the requirements:

  • Contains lowercase letters (o, g, p, z, i)
  • Contains uppercase letters (J, H, Z, I)
  • Contains digits (4, 8)
  • Contains special characters ((, ()
  • Is within the required length of 8–20 characters

What happened instead?

The validation failed, and the string did not pass. I’m not sure why the regex isn’t matching the string, even though all the conditions seem to be fulfilled.

2

Answers


  1. The last part of your regex ([A-Za-zd@$!%*?&]+) matches only the characters specified within it and there are no parentheses.

    You should use .+ instead to match all the characters because the validation has already been done before.

    Login or Signup to reply.
  2. I would suggest a few changes to improve your pattern:

    • Use p{Ll} instead of [a-z] to match a lowercase letter in any
      language. And use p{Lu} instead of [A-Z] to match an uppercase
      letter. You have to enable the unicode flag (or the new v flag).
    • Accept more special characters. Ex: [](){}|@$£#!%&?+-*/:;,.~"'<>.
    • Accept spaces, mainly because remembering a long sentence is easier
      than a complicated password.
      Example: "I see 3 rabbits & a duck. And you?"
    • Perhaps push up the limit of 20 characters to 32 or 64 if it doesn’t
      harm your app.

    The commented pattern (PCRE flavor):

    ^
    # Should contain at least a lowercase letter in any language.
    (?=.*p{Ll})
    # Should contain at least an uppercase letter in any language.
    (?=.*p{Lu})
    # Should contain at least a digit.
    (?=.*d)
    # Should contain at least one of these special characters.
    (?=.*[[](){}|@$£#!%&?+-*/:;,.~"'<>])
    # Must be between 8 and 20 characters long.
    .{8,20}
    $
    

    Here, I replaced the accepted chars by .{8,20}, meaning that it will
    accept anything. I kept it simple to make the regex short and readable.

    The same regex in JavaScript:

    const regexValidPassword = /^(?=.*p{Ll})(?=.*p{Lu})(?=.*d)(?=.*[[](){}|@$£#!%&?+-*/:;,.~"'<>]).{8,20}$/u;
    
    [
      'hkG34Zt!',
      'zi-3aaAu79',
      'àÉ|12345',
      'Привіт!3',
      'Γεια σας@66',
      '3, 2, 1, ready, Go!',
      "abctfgh",  // With a tab
      "abcnfgh",  // With a newline
      '1aB%',
      'àÉ^|12345', // With a special char not in our list.
      'abcdefghij#ABCDEFGHIJ#1234567(to long)',
      'ABCDEFG123',
      '$ £ []()'
    ].forEach((password) => {
      console.log({password: password, valid: regexValidPassword.test(password)});
    });

    Now, a safer version of the regex would be to replace . by
    a white-list of chars. Example, allowing spaces also:

    [p{Ll}p{Lu}d [](){}|@$£#!%&?+-*/:;,.~"'<>]{8,64}

    Tested with the same JS as before:

    const regexValidPassword = /^(?=.*p{Ll})(?=.*p{Lu})(?=.*d)(?=.*[[](){}|@$£#!%&?+-*/:;,.~"'<>])[p{Ll}p{Lu}d [](){}|@$£#!%&?+-*/:;,.~"'<>]{8,64}$/u;
    
    [
      'hkG34Zt!',
      'zi-3aaAu79',
      'àÉ|12345',
      'Привіт!3',
      'Γεια σας@66',
      '3, 2, 1, ready, Go!',
      "abctfgh",  // With a tab
      "abcnfgh",  // With a newline
      '1aB%',
      'àÉ^|12345', // With a special char not in our list.
      'abcdefghij#ABCDEFGHIJ#1234567(to long)',
      'ABCDEFG123',
      '$ £ []()'
    ].forEach((password) => {
      console.log({password: password, valid: regexValidPassword.test(password)});
    });
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search