Sign Up Docs Tech Blog Product Blog

Easy breached password detection

Breached password detection protects your users by making sure they aren’t using passwords that have been seen in previous breaches. An easy way to provide it to your users is:

const {pwnedPassword} = require('hibp');

async function hasPasswordBeenUsedBefore(password) {
    // HIBP asks that you identify yourself through the user agent
    const numTimesUsedBefore = await pwnedPassword(password, {userAgent: "whoami"});
    return numTimesUsedBefore > 0;

HIBP (or have i been pwned?) is a service that provides APIs on top of past breaches. In the code snippet, I used the library hibp, but there are existing libraries for pretty much every language. The API is easy to use if you prefer to not rely on a library here. Let’s test our function:

async function testPasswords() {
    const passwords = ["password", "p455w0rd!!", "iamsosecure", "48lU!#S032XN2tzbh"]
    for (const password of passwords) {
        const usedBefore = await hasPasswordBeenUsedBefore(password);
        console.log(password, usedBefore);

// password true
// p455w0rd!! true
// iamsosecure false
// 48lU!#S032XN2tzbh false

We can see that password is a pretty easy thing to catch, whereas p455w0rd!! is more subtle and would have passed any “8 characters, at least 1 number and special character” requirements.

Neither iamsosecure or 48lU!#S032XN2tzbh were in any past breaches, which is important to note. This isn’t a guarantee that the password is high quality, just that it hasn’t been seen in a breach before. However, there are currently over 600M easily attackable passwords that you will now disallow in only a few lines of code.

Quick plug: PropelAuth provides this and other security features. If you don’t want to deal with auth and want to just get back to building your product, we’d appreciate if you checked it out.

Technical note: Is the password sent in plaintext?#

No, the API is designed to not accept plaintext passwords. Instead, you send the first 5 characters of the SHA-1 hash of the password. The response is a list of SHA-1 hash suffixes (everything after the first 5 characters), that begin with those 5 characters. You can check your hash against this list locally.

Example from the API docs

GET{first 5 hash chars}