43

I need to validate the strength of a password input form field. The requirements are:

  • at least one lowercase char
  • at least one uppercase char
  • at least one number
    (no matter the order)

What I have searched and tried so far goes below, the results are inconsistent.

It seems to validate the order of the regex validation.
What I need is to just check if at least one of the char "types" are present.

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
    selector: 'signup',
    templateUrl: './signup.component.html',
    styleUrls: ['./signup.component.scss']
})
export class SignupComponent {

    form: FormGroup;

    constructor() {
        this.init();
    }

    init() {
        this.form = this.fb.group({
            name: ['', [Validators.required]],
            email: ['', [Validators.required, Validators.email],
            password: ['', [
                Validators.required, 
                Validators.pattern('((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,30})')
            ]]
        }); 
    }
}
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
guillefd
  • 1,903
  • 1
  • 16
  • 26

6 Answers6

57

I took a stab at this with Angular's built-in pattern validator and was able to come up with the following that checks for:

  • At least 8 characters in length
  • Lowercase letters
  • Uppercase letters
  • Numbers
  • Special characters

    password: [
      '',
      [
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&].{8,}')
       ]
    ]
    

I'll add that I'm by no means a regex expert. This is simply what worked for me for a closely related case to the OP. Maybe it will help someone else. Mozilla's documentation helped a lot in figuring this out, specifically the Writing a regular expression pattern section.

Desmond
  • 1,532
  • 15
  • 25
  • 9
    Nice solution. Two remarks although. Your regex require 9 characters length because of a dot before quantifier. It allows to use only special characters listed in regex. Here is my updated version which allows any special character but requires at least one listed in regex (?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&]).{8,} – Semyon Oct 19 '18 at 10:06
  • I agree that last dot can be removed then – Davious Nov 19 '18 at 18:48
  • 1
    This is not what the OP requested, he requested : - at least one lowercase char - at least one uppercase char - at least one number This is a valid pattern for what the OP requested: Validators.pattern('((?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,30})') – Sytham May 21 '19 at 14:31
  • Your expression is the only one that worked on my ionic 4 application. Thanks alot – Ian Samz Aug 07 '19 at 12:07
25

I´ve not been able to use correctly the Validator Pattern, so I made a Custom Validator, and validate the password field string with three simple regex.
Anyway, I look forward to use correctly the Angular Validator Pattern.

Custom Validator

// password.validator.ts

import { FormControl } from '@angular/forms';

export interface ValidationResult {
    [key: string]: boolean;
}

export class PasswordValidator {

    public static strong(control: FormControl): ValidationResult {
        let hasNumber = /\d/.test(control.value);
        let hasUpper = /[A-Z]/.test(control.value);
        let hasLower = /[a-z]/.test(control.value);
        // console.log('Num, Upp, Low', hasNumber, hasUpper, hasLower);
        const valid = hasNumber && hasUpper && hasLower;
        if (!valid) {
            // return what´s not valid
            return { strong: true };
        }
        return null;
    }
}

Replaced the Validator Pattern with my Custom Validator

// signup.component.ts

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { PasswordValidator } from 'validators/password.validator';

@Component({
    selector: 'signup',
    templateUrl: './signup.component.html',
    styleUrls: ['./signup.component.scss']
})
export class SignupComponent {

    form: FormGroup;

    constructor() {
        this.init();
    }

    init() {
        this.form = this.fb.group({
            name: ['', [Validators.required]],
            email: ['', [Validators.required, Validators.email],
            password: ['', [
                Validators.required, 
                PasswordValidator.strong
            ]]
        }); 
    }
}
guillefd
  • 1,903
  • 1
  • 16
  • 26
20

If you have a look at Validator.js, you will notice that you may pass both a string or a regex literal to the Validators.pattern.

Regex passed as a string literal

  • The whole string must match the pattern (i.e. the pattern is anchored on both sides)
  • Backslashes can form string escape sequences and you must use double backslashes to define a literal backslash that is used to define a regex escape. So, to define a digit matching pattern, use '\\d', to define a whitespace pattern use '\\s', to define a backslash, use '\\\\'.

Regex passed as a regex literal

  • The regex does not automatically require a full string match.
  • Use single backslashes to define regex escapes (e.g. /\s+/)

So, what you may use is either of the two:

this.form = this.fb.group({
    name: ['', [Validators.required]],
    email: ['', [Validators.required, Validators.email],
    password: ['', [
        Validators.required, 
        Validators.pattern('(?=\\D*\\d)(?=[^a-z]*[a-z])(?=[^A-Z]*[A-Z]).{8,30}')
 ]]
});

Or

this.form = this.fb.group({
    name: ['', [Validators.required]],
    email: ['', [Validators.required, Validators.email],
    password: ['', [
        Validators.required, 
        Validators.pattern(/^(?=\D*\d)(?=[^a-z]*[a-z])(?=[^A-Z]*[A-Z]).{8,30}$/)
 ]]
});

Regex details

Note that the pattern changes I suggest are merely those related to the principle of contrast:

  • ^ - start of string (implicit in string regex pattern)
  • (?=\D*\d) - there must be 1 digit
  • (?=[^a-z]*[a-z]) - there must be 1 lowercase ASCII letter
  • (?=[^A-Z]*[A-Z]) - there must be 1 uppercase ASCII letter
  • .{8,30} - any 8 to 30 chars other than line break chars
  • $ - end of string (implicit in string regex pattern).
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
8

Below Solution Considers that the parameters for the password requirement may be dynamic

The approach dynamically generates the pattern string

  passRequirement = {
    passwordMinLowerCase: 1,
    passwordMinNumber: 1,
    passwordMinSymbol: 2,
    passwordMinUpperCase: 1,
    passwordMinCharacters: 8
  };
  pattern = [
    `(?=([^a-z]*[a-z])\{${this.passRequirement.passwordMinLowerCase},\})`,
    `(?=([^A-Z]*[A-Z])\{${this.passRequirement.passwordMinUpperCase},\})`,
    `(?=([^0-9]*[0-9])\{${this.passRequirement.passwordMinNumber},\})`,
    `(?=(\.\*[\$\@\$\!\%\*\?\&])\{${this.passRequirement.passwordMinSymbol},\})`,
    `[A-Za-z\\d\$\@\$\!\%\*\?\&\.]{${
      this.passRequirement.passwordMinCharacters
    },}`
  ]
    .map(item => item.toString())
    .join("");
  resetPwdForm = this.fb.group({
    newpwdctrlname: ['Passwod1@@5', [Validators.required, Validators.pattern(this.pattern)]],
    shownewpwdctrlname: ['', []],
    rptpwdctrlname: ['', [Validators.required]]
  });
  constructor (private fb: FormBuilder) {}

See this demo on stackblitz

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
  • 1
    Hello, thank you very much for your contribution, how would you add the following to the validation? That no sequence of more than two numbers is allowed eg: 123, 321 That qwerty type strings are not allowed ex: asdf Thank you very much in advance. – Fernando Arbelo Jan 22 '21 at 22:25
  • @FernandoArbelo please create a new question, you can add a link in the comment I can have a look – Owen Kelvin Jan 23 '21 at 06:06
  • 3
    Thank you very much. I consider your solution as more complete because you can parameterize password strength validation. And also because you provided working demo. Thumbs up! – Tjodalv Mar 10 '21 at 13:00
0

Just to be thorough, here is a Validator regex that actually works and contains all symbols:

must have a number, must have a lowercase, must have an uppercase, and must have one of any other symbol possible on the standard qwerty keyboard.

Make sure in typescript or javascript that every slash is doubled properly.

Validators.pattern('(?=.*[0-9])(?=.*[A-Z])(?=.*[a-z])(?=.*[`~!@#$%^&\\\\*\\\\(\\\\)\\\\-_=\\\\+\\\\{\\\\}\\\\[\\\\]|\\\\\\\\:;"\\'<>,\\\\.?\\\\/]).{8,}')
user12986714
  • 741
  • 2
  • 8
  • 19
dykstrad
  • 398
  • 3
  • 10
0
Validators.pattern(/^(?=\D*\d)(?=[^a-z]*[a-z])(?=.*[$@$!%*?&])(?=[^A-Z]*[A-Z]).{8,30}$/)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
SamJP
  • 1