Safeguard
Security

Password Validation: How to Verify Credentials Securely

Password validation is more than a regex for length and symbols. Here is how to check credentials the way modern guidance actually recommends.

Aisha Rahman
Security Analyst
6 min read

Password validation is the set of checks an application runs to confirm a password is acceptable to create and correct to authenticate — and doing it well means dropping most of the composition rules you were taught and adding a breached-password check instead. The old model of "one uppercase, one number, one symbol, change every 90 days" is now considered actively harmful by the standards bodies that once recommended it. This guide covers what modern password validation should do at signup and at login.

There are two distinct moments people conflate. Validation at creation decides whether a new password is allowed. Validation at authentication decides whether the presented password matches the stored credential. They use different logic, and getting them mixed up is a common source of both weak security and bad UX.

What NIST actually recommends now

NIST Special Publication 800-63B rewrote the rulebook. The guidance that matters for validation:

  • Allow passwords between 8 and at least 64 characters. Longer is better and length beats complexity.
  • Accept all printable ASCII and Unicode, including spaces and emoji. Do not strip or silently truncate.
  • Do not impose composition rules (forced symbols, mixed case, digits). They push users toward predictable patterns like Password1!.
  • Do not require periodic rotation unless there is evidence of compromise.
  • Screen new passwords against a list of known-breached and common values.

That last point is the single most valuable check you can add. A 40-character passphrase that happens to be a leaked song lyric is weaker than a random 12-character string, and only a breach check catches it.

Screening against breached passwords

The practical way to screen without shipping a giant wordlist or sending plaintext anywhere is a k-anonymity range query, the model the Have I Been Pwned Pwned Passwords API popularized. You hash the candidate password with SHA-1, send only the first five hex characters of the hash, and receive back every suffix in that bucket. You compare locally.

import hashlib
import requests

def is_breached(password: str) -> bool:
    sha1 = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()
    prefix, suffix = sha1[:5], sha1[5:]
    resp = requests.get(f"https://api.pwnedpasswords.com/range/{prefix}")
    for line in resp.text.splitlines():
        hash_suffix, count = line.split(":")
        if hash_suffix == suffix:
            return int(count) > 0
    return False

The full password never leaves your server, and the API never sees enough to reconstruct it. Run this at creation and at password change, and reject anything with a nonzero breach count.

Store credentials so validation stays safe

Password validation at login is only as safe as how you stored the credential in the first place. Never store plaintext, never store reversibly encrypted passwords, and never store a fast hash like plain SHA-256. Use a memory-hard, purpose-built password hashing function:

  • Argon2id is the current first choice for new systems.
  • scrypt is a solid alternative where Argon2 is unavailable.
  • bcrypt remains acceptable, with the caveat that it truncates input at 72 bytes, so validate length before hashing.
from argon2 import PasswordHasher

ph = PasswordHasher()  # sensible memory/time defaults

# at creation
stored = ph.hash(user_password)

# at authentication
try:
    ph.verify(stored, presented_password)
    if ph.check_needs_rehash(stored):
        stored = ph.hash(presented_password)  # upgrade parameters over time
except Exception:
    reject_login()

The check_needs_rehash step matters: it lets you raise cost parameters as hardware improves without forcing a password reset. If your dependency graph pulls in an outdated crypto library, an SCA tool such as Safeguard can flag it before a weak default ships.

Validate the flow, not just the string

Composition rules are only part of validation. The login path itself needs guardrails so an attacker cannot brute-force valid credentials:

  1. Rate limit authentication attempts per account and per source, with exponential backoff.
  2. Use constant-time comparison for any token or secret comparison to avoid timing leaks. The password hash verify call already does this; do not hand-roll == on secrets.
  3. Return a generic failure — "invalid username or password" — so validation errors do not tell an attacker which half was wrong.
  4. Log failures with enough context to detect credential stuffing, but never log the password itself.

Client-side validation is UX, not security

You can and should give instant feedback in the browser — a strength meter, a length hint, a breached-password warning. But treat every client-side check as advisory. All authoritative password validation runs on the server, because a client control is trivially bypassed by anyone hitting your API directly. If your only length check is a maxlength attribute on an input, you do not have password validation; you have a suggestion.

A useful pattern is to expose the same validation endpoint the signup form calls so the browser and server share one source of truth, rather than duplicating rules that drift apart over time.

Common validation mistakes to retire

A few patterns still show up in code reviews that should be removed:

  • Silently truncating long passwords, so correcthorsebatterystaple... and its prefix both authenticate.
  • Disallowing spaces or "special" characters, which blocks passphrases and password managers.
  • Forcing 90-day rotation, which produces Summer2025! then Summer2026!.
  • Storing a password hint in plaintext, which is often just the password rephrased.
  • Blocking paste in the password field, which breaks password managers and pushes users toward weaker, memorable passwords.

If you want to go deeper on authentication design and hashing tradeoffs, the Academy has a module on credential storage, and our SCA product will surface outdated crypto dependencies before they reach production.

FAQ

What is the minimum password length I should enforce?

At least 8 characters, and allow up to 64 or more. NIST treats 8 as a floor, not a target — encourage longer passphrases, and never impose a maximum below 64.

Should I still require symbols and mixed case?

No. Composition rules push users toward predictable substitutions and measurably weaken passwords. Replace them with a length minimum and a breached-password screen, which catches the actually-weak choices.

Is bcrypt still safe for password validation?

Yes, bcrypt remains acceptable, but be aware it truncates input at 72 bytes and validate length before hashing. For new systems, Argon2id is the preferred choice because it is memory-hard and resists GPU-based cracking.

Do I need to force password rotation?

Not on a schedule. Rotate only when there is evidence of compromise. Scheduled rotation leads to incremental, guessable changes and creates more support burden than security benefit.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.