Verify password with a single regex
Tags: regex, javascript
Published: 2022-06-16 13:16:14

Probably, you've ever encountered some circumstances to check a user's input, such as password, before submitting it to the server.

we usually write verbose code to check complex rules, such as _there must be at least one special character_ etc.

is there any way to achieve this with a single line of code?

**the answer is, yes.**

## Let's make the problem clear

There are many special rules for [password security](https://en.wikipedia.org/wiki/Password_strength), but for now, we will focus on only the below rules.

1. password must contain at least one number
2. password must contain at least one small character
3. password must contain at least one capital character
4. password must contain at least one special character
5. password must be longer than 8 characters

to be more precise, I will use only `!`, `@`, and `#` as 'special characters' in this case since the regex part would be harder to read if it contains too many special characters and escapes.

but adding more special characters would be not too hard after understanding the basic idea.

## Let's have some try

First, let's start from rule 5. we need to check given string has consisted of more than 8 of any character.

```js
/^.{8,}$/
```

what we need is, however, not just _any character_. we can apply more conditions by using the [character class](https://www.regular-expressions.info/charclass.html).

```js
/^[\da-zA-Z!@#]{8,}$/
```

this pattern will match strings that consisted of more than 8 numbers or upper or lower characters or special characters.

so far it looks like following our rules well, there is a little fault.

**it does not guarantee** that there is at least one character from each category.

```js
/^[\da-zA-Z!@#]{8,}$/.test("12345678") // true
```

you may find it is hard to solve.

fortunately, we can do it with a single regex!

## Positive lookahead

`?=` is a [positive lookahead syntax](https://www.regular-expressions.info/lookaround.html), meaning the matched pattern is followed by what is inside the `(?=)` parentheses, which is not captured itself.

The below example will capture only protocol type, excluding the `://` part.

```js
'http://example.com'.match(/\w+(?=:\/\/)/)[0] // 'http'
```

it is also a [zero-width assertion](https://www.regular-expressions.info/lookaround.html), which means you can apply additional conditions to the following pattern.

for example, a valid [javascript identifier](https://developer.mozilla.org/en-US/docs/Glossary/Identifier) can contain numbers, but shouldn't start with numbers. _(real condition is more complicated than this, but we'll ignore other rules now for simplification)_

now we can simply write a regex that indicates whether the given identifier is valid or not.

```js
['abc', 'var_name', 'x', '123hello', '$', 'foo123']
    .filter(_ => /^(?=\D)[$\w]+$/.test(_))
// [ 'abc', 'var_name', 'x', '$', 'foo123' ]
// '123hello' was invaild.
```

of course, we can write it like this, but it is too verbose than the first one.

```js
/^[$_a-zA-Z][$\w]*$/
```

## So how can we assert that it contains at least one character?

Let's proceed step by step.

`(?=x)` will match if the string exactly starts with `x`.

but we need to know whether there is `x` or not somewhere not only in the first.

another thing we can come up with is matching with `.*` for _other_ characters.

for example, `/(?=.*\d)\w+/` will match any string that contains at least one number.

well, now we can apply this for our original purpose.

```js
/^(?=.*\d)[a-z\d]+$/
```

this one will match against any password which contains at least one number, consisting of lowercase characters or numbers as well.

and we can add conditions more and more, our final version will look like this:

```js
/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#])[\da-zA-Z!@#]{8,}$/
```

take a slow look at the above regex. you can find that just the same pattern is repeated. then you will finally understand the idea.

this is a little bit weird at first, but a useful trick that you should know.

hope this was helpful for those who have difficulty with regex, and keep happy coding! 😎