It is common for passwords to have a set of requirements. The most obvious
requirement being a minimum length, but another common requirement is for
the password to at least include a certain amount of characters of a certain
category, like uppercase and lowercase alphabetic characters, numbers and/or
other special characters. Though, nowadays, this last type of requirement isdiscouraged by security experts.
This module provides an API which enables you to set up your own
PasswordPolicy to validate the format of Passwords.
Enforcing inclusion of specific character types (like special characters,
numbers, lowercase and uppercase letters) actually makes passwords less secure.
The length of a password is the most important factor, so let
users make their passwords as lengthy as they want, within reason.
(keep in mind some algorithms have length limitations, like bcrypt's
72 character limit)
Do allow spaces so users can use sentences for passwords.
Showing the "strength" of user's passwords is advised. A good algorithm
to use is zxcvbn.
The best way to mitigate online attacks is to limit the rate of login attempts.
Password Policies
The most important part is to have a valid and robust PasswordPolicy.
A defaultPasswordPolicy_ is provided to quickly set up a NIST recommended
validation of passwords, but you can also adjust it, or just create your
own.
Just remember that a PasswordPolicy must be validated first to make
sure it is actually a ValidPasswordPolicy. Otherwise, you'd never be
able to validate any given Passwords.
Example usage
So let's say we're fine with the default policy, which requires the
password to be between 8-64 characters, and doesn't enforce any specific
character category usage, then our function would look like the following:
But, for example, if you'd like to enforce that a Password includes
at least one special character, and be at least 12 characters long,
you'll have to make your own PasswordPolicy.
This custom policy will then have to be validated first, so it can be
used to validate Passwords further on.
Template Haskell
The easiest way to validate a custom PasswordPolicy is by using a
Template Haskell splice.
Just turn on the {-# LANGUAGE TemplateHaskell #-} pragma, pass your
policy to validatePasswordPolicyTH, surround it by $(...) and if
it compiles it will be a ValidPasswordPolicy.
Another way of validating your custom policy is validatePasswordPolicy.
In an application, this might be implemented in the following way.
main :: IO ()
main =
case (validatePasswordPolicy customPolicy) of
Left reasons -> error $ show reasons
Right validPolicy -> app `runReaderT` validPolicy
customValidateFunc :: Password -> ReaderT ValidPasswordPolicy IO Bool
customValidateFunc pwd = do
policy <- ask
return $ isValidPassword policy pwd
Let's get dangerous
Or, if you like living on the edge, you could also just match on Right.
I hope you're certain your policy is valid, though. So please have at least
a unit test to verify that passing your PasswordPolicy to
validatePasswordPolicy actually returns a Right.
The main function of this module is probably isValidPassword,
as it is simple and straightforward.
Though if you'd want to know why a Password failed to validate,
because you'd maybe like to communicate those InvalidReasons
back to the user, validatePassword is here to help you out.
Next to the obvious lower and upper bounds for the length of a Password,
a PasswordPolicy can dictate how many lowercase letters, uppercase letters,
digits and/or special characters are minimally required to be used in the
Password to be considered a valid Password.
An observant user might have also seen that a PasswordPolicy includes a
CharSetPredicate. Very few users will want to change this from the
defaultCharSetPredicate, since this includes all non-control ASCII characters.
If, for some reason, you'd like to accept more characters (e.g. é, ø, か, 事)
or maybe you want to only allow alpha-numeric characters, charSetPredicate is
the place to do so.
If any other field has a negative value (e.g. lowercaseChars), it will be defaulted to 0
The total sum of all character category values (i.e. all fields ending in -Chars)
must not be larger than the value of maximumLength.
The provided CharSetPredicate needs to allow at least one of the characters in the
categories which require more than 0 characters. (e.g. if lowercaseChars is > 0,
the charSetPredicate must allow at least one of the characters in ['a'..'z'])
or else the validation functions will return one or more InvalidPolicyReasons.
Do note that this being a default policy doesn't make it a goodenough policy in every situation. The most important field, minimumLength,has 8 characters as the default, because it is the bare minimum for somesense of security. The longer the password, the more difficult it will beto guess or brute-force, so a minimum of 12 or 16 would be advised ina production setting.
The default character set consists of uppercase and lowercase letters, numbers,
and special characters from the ASCII character set.
(i.e. everything from the ASCII set except the control characters)
Validate CharSetPredicate to return True on at least one of the characters
that is required.
For instance, if PasswordPolicy states that the password requires at least
one uppercase letter, then CharSetPredicate should return True on at least
one uppercase letter.