# auth.password-policy Decide whether a new password is acceptable, and say why not in words a form can show. The same function runs in the browser as the person types and on the server, which has the final say, so the two can never disagree: ``` policy = passwordPolicy("nist-800-63b-4-single-factor") check = checkPassword(password, email, name, policy) # {valid: false, failures: [{code: "too_short", message: "Use at least 15 characters."}, ...]} ``` It is a group because a policy is only useful to `checkPassword`; `passwordPolicy` looks one up by name from the data, so both sides of an application name the policy rather than copying its numbers. ## Policies (data/policies.json) | name | min | max | classes | source | |---|---:|---:|---:|---| | `nist-800-63b-4-single-factor` | 15 | 128 | 0 | NIST SP 800-63B-4 ยง3.1.1.2: a password that is the only factor SHALL be at least 15 characters | | `nist-800-63b-4-multi-factor` | 8 | 128 | 0 | the same section: 8 when a second factor is also required | | `composition-12-3` | 12 | 128 | 3 | for organisations whose own rules still demand character classes | All three check the common-password list and personal words. NIST SP 800-63B-4 (26 August 2025) says verifiers SHALL NOT impose composition rules and SHOULD permit at least 64 characters; the maximum here is 128, which bounds the work a hash does without refusing any real passphrase. Figures checked against https://pages.nist.gov/800-63-4/sp800-63b.html. A new revision will be a new policy name in a new version, never an edit of these. A caller may also pass its own `PasswordPolicy` record; nonsense numbers (a minimum below 1, a maximum below the minimum, more than 4 classes) throw. ## Rules, in the order failures are reported 1. `too_short` / `too_long`: length in characters, meaning Unicode code points, as NIST specifies. An emoji is one character, not the two UTF-16 units JavaScript's `length` counts. 2. `too_few_character_classes`: lower-case a-z, capitals A-Z, digits 0-9, and everything else (symbols, spaces, and any non-ASCII letter). Only checked when the policy asks for classes. 3. `too_common`: the password, with A-Z folded to a-z, is on the list in `data/common-passwords.json` (exact match, not substring). 4. `contains_email`: the password contains the email's local part, or any piece of it between punctuation (`ada.lovelace@...` gives `ada.lovelace`, `ada` and `lovelace`), ignoring case. 5. `contains_name`: the password contains any word of the name, ignoring case. Pieces shorter than 3 characters are ignored in both, since refusing every password that contains "al" helps nobody. Every broken rule is listed, not just the first, so a form can show them all at once. Messages are plain sentences meant for the person choosing the password; the codes are stable for code to branch on. Case folding is ASCII only, so every language folds identically. ## The common-password list The 1,000 most common distinct passwords of 8 or more characters from the UK National Cyber Security Centre's list of the 100,000 most common passwords in Have I Been Pwned's breach corpus (NCSC, "Passwords, passwords everywhere", April 2019, `PwnedPasswordsTop100k.txt`), lower-cased and de-duplicated, with each entry's rank in that list (the last one is rank 2,902). ncsc.gov.uk was unavailable while this was built, so the file was taken from the verbatim mirror in SecLists (`Passwords/Common-Credentials/100k-most-used-passwords-NCSC.txt`). Shorter entries are left out because every policy here refuses them for length already. A 15-character minimum makes the list matter much less; that is the point of NIST's longer minimum. A full breached-password check (such as the Pwned Passwords range API) needs the network and belongs in the application, not here. Sources: NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management, section 3.1.1.2 (https://pages.nist.gov/800-63-4/sp800-63b.html); NCSC, Top 100k passwords (https://www.ncsc.gov.uk/static-assets/documents/PwnedPasswordsTop100k.txt, mirrored at https://github.com/danielmiessler/SecLists).