Hiệp Chelsea
Hiệp Chelsea

Reputation: 45

Use RegEx to validate a password on iOS Swift

I need to validate a user's password against the following requirements:

Does anyone know how I can accomplish this using a RegEx?

I've made attempts to solve this problem on my own, but nothing I've tried so far as worked. The code for my latest attempt is below.

func isPasswordHasEightCharacter(password: String) -> Bool {
    let passWordRegEx = "^.{8,}$"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passWordRegEx)
    return passwordTest.evaluate(with: password)
}

func isPasswordHasNumberAndCharacter(password: String) -> Bool {
    let passRegEx = "^(?=.*[a-z])(?=.*[0-9])"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passRegEx)
    return passwordTest.evaluate(with: password)
}

func isPasswordHasNumberAndCharacterSign(password: String) -> Bool {
    let passWordRegEx = "^(?!.[^a-zA-Z0-9@#${'$'}^+=])"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passWordRegEx)
    return passwordTest.evaluate(with: password)
}

Upvotes: 3

Views: 6901

Answers (2)

Joakim Danielson
Joakim Danielson

Reputation: 51861

In this solution each requirement is checked individually to avoid complex regular expressions. This solution supports variants of characters like ôöệż etc

func validatePassword(_ password: String) -> Bool {
    //At least 8 characters
    if password.count < 8 {
        return false
    }

    //At least one digit
    if password.range(of: #"\d+"#, options: .regularExpression) == nil {
        return false
    }

    //At least one letter
    if password.range(of: #"\p{Alphabetic}+"#, options: .regularExpression) == nil {
        return false
    }

    //No whitespace charcters
    if password.range(of: #"\s+"#, options: .regularExpression) != nil {
        return false
    }

    return true
}

Some test cases

print(validatePassword("abc"))        // --> false
print(validatePassword("abcdefgh"))   // --> false
print(validatePassword("abcde fgh1")) // --> false
print(validatePassword("abcdefgh1"))  // --> true
print(validatePassword("abcåäö123"))  // --> true
print(validatePassword("ABC123€%&"))  // --> true
print(validatePassword("@èệżôøö123")) // --> true

Upvotes: 1

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 626689

The main issue is that NSPredicate with MATCHES requires the full string to match and consume the whole input. Lookarounds - you are using lookaheads - do not consume text, that is, the texts they match are not added to the match value and the regex index stays where it was before attempting to match a lookaround pattern.

The last two parts can be thus fixed this way:

func isPasswordHasNumberAndCharacter(password: String) -> Bool {
    let passRegEx = "(?=[^a-z]*[a-z])[^0-9]*[0-9].*"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passRegEx)
    return passwordTest.evaluate(with: password)
}

func isPasswordHasNumberAndCharacterSign(password: String) -> Bool {
    let passWordRegEx = "[a-zA-Z0-9!@#$%^&*]+"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passWordRegEx)
    return passwordTest.evaluate(with: password)
}

The first part is OK, though you do not need the ^ and $ anchors (as the whole string input must match the pattern). However, to check a string length you do not even need a regex: see Get the length of a String.

Note:

  • ^(?=[^a-z]*[a-z])[^0-9]*[0-9].*\z matches a string that contains at least one lowercase ASCII letter and at least one ASCII digit
  • ^[a-zA-Z0-9!@#$%^&*]+$ will match a string that only contains ASCII letters, digits and some specific symbols. If you inlude a - make sure it is at the end. Escape both [ and ] if you need to add them, too.

If you want to combine all that into 1 regex you could use

let passRegEx = "(?=[^a-z]*[a-z])(?=[^0-9]*[0-9])[a-zA-Z0-9!@#$%^&*]{8,}"

Or, if you are not using the regex with the NSPredicate MATCHES, with anchors:

let passRegEx = "\\A(?=[^a-z]*[a-z])(?=[^0-9]*[0-9])[a-zA-Z0-9!@#$%^&*]{8,}\\z"

Upvotes: 2

Related Questions