DEV Community

Cover image for Pwnd Password Checking on the Edge
Karl L. Hughes for StackPath

Posted on

Pwnd Password Checking on the Edge

One of the most effective ways to secure your users’ data is to ensure that they select a strong password. Unfortunately, putting the burden on your users rarely works:

“The bad news is that most users don't pick strong passwords. This has been proven time and time again...Even worse, most users re-use these bad passwords across multiple websites.” - Jeff Atwood, Founder of StackOverflow

Many application developers implement password rules that require a particular password length, a combination of characters, or various forms of capitalization to address this issue. Some of these commonly-used password rules are not that helpful, but long, hard to guess passwords are generally good for security. Checking password length is easy enough, but how can you ensure that the user’s password is hard to guess?

One method is to check if they’re using a password that’s been compromised before.

If you’ve worked in web development for long, you may have heard of the NIST security guidelines. In their Digital Identity Guidelines, they specify:

“When processing requests to establish and change memorized secrets, verifiers SHALL compare the prospective secrets against a list that contains values known to be commonly-used, expected, or compromised. For example, the list MAY include, but is not limited to: Passwords obtained from previous breach corpuses.” - NIST Digital Identity Guidelines

When users set their passwords, NIST recommends that you ensure they don’t use a password previously exposed in a data breach. While you might hear about some large data breaches on the news, there’s no way you can keep up with all the data breaches happening around the world. If you tried, you’d never have time to get any coding done!

Have I Been Pwned?
A few years ago, Troy Hunt - a well-known security researcher - set up a website to address this very issue. Users can enter their password on HaveIBeenPwned.com to see if it’s been part of a data breach and, therefore, not safe to use.

Unfortunately, this isn’t something most people do, but Have I Been Pwned also has an API. Using the REST API, you can check that a user signing up for your application does not use a compromised password before you let them register. When used in addition to requiring a long, sufficiently random password, this can significantly increase your platform’s data security and minimize brute force password attacks.

Checking for Pwned Passwords on the Edge
If you’re using an authentication service, you will need to figure out the best way to implement password strength checking into your signup flow. While you could modify your authentication flow, you can also host your pwned password checking service on the edge with StackPath.

Enforcing strict password rules will annoy some users, but you can minimize this annoyance by making responses as fast as possible. In this tutorial, you’ll see how to use StackPath’s edge hosting and the Pwned Password API to ensure that users are signing up with uncompromised passwords, and you'll see how edge hosting can offer a 14x performance improvement over traditional hosting when caching responses.

Creating the Node Application

To demonstrate the use of the Pwned Password API on StackPath, I’ve created an Express application in a Docker image. This project's code is available on Github if you’d like to follow along. It uses the same Dockerfile and Express server outlined here, so you may want to start by reading that tutorial if you’re not familiar with Node or Docker.

Signup requests from a web browser will be sent to the Node application. When the client posts a password, the Node app will call the Pwned Password API. The whole flow can be visualized here:

The Pwned Password API takes the first five characters of a SHA1 hash of the password and returns a list of hashed password suffixes to the Node application. This use of a partial hash minimizes any risk in posting secure data to a third-party service.

If you want to see the raw results that the Pwned Password API returns, visit https://api.pwnedpasswords.com/range/21BD1 in your browser. You will see a list of hashes followed by the number of times they’ve been seen in plain text on the internet:

0018A45C4D1DEF81644B54AB7F969B88D65:1
00D4F6E8FA6EECAD2A3AA415EEC418D38EC:2
011053FD0102E94D6AE2F8B83D76FAF94F6:1
...
Enter fullscreen mode Exit fullscreen mode

In order to create the partial hash, prefixes, and suffixes in Node, you can use the sha1 library and built-in substring function:

// Shah-1 hash the password and break it into a prefix and suffix
const hashedPassword = sha1(password).toUpperCase();
const prefix = hashedPassword.substring(0, 5);
const suffix = hashedPassword.substring(5);
Enter fullscreen mode Exit fullscreen mode

Next, call the Pwned Password API using the prefix and check for the presence of suffix in the response. In this case, I’m using the node-fetch library, but you can use whichever HTTP request package you prefer.

Because performance is one of the primary reasons to use edge hosting for password validation, you can cache the response from the Pwned Password API. While caching hashed passwords would normally have some security implications, these are pubicly available compromised password hashes, so it's less of a concern.

Here's the code that handles caching and checking against the API's results:

// Check against cache
const cachedResults = hashedPasswordCache.get(prefix);
if (cachedResults) {
  // Check if the cached result includes the suffix of the hash
  return generateResponse(res, cachedResults, suffix);
} else {
  // Send the first 5 chars to pwned password API
  fetch('https://api.pwnedpasswords.com/range/' + prefix)
    .then(tmp => tmp.text())
    .then(body => {
      // Cache the response for future use
      hashedPasswordCache.set(prefix, body);
      // Check if the response includes the suffix of the hash
      return generateResponse(res, body, suffix);
    }).catch(err => {
    // Handle any API errors
    console.error(err);
    res.status(400).json({message: 'Something went wrong.'});
  });
}
Enter fullscreen mode Exit fullscreen mode

This demo lets the user know if their password has been pwned or not, but in production, you probably want to pass valid usernames and passwords along to your authentication service to create the user’s account and log them in.

Next, you need to build your application as a Docker image. Open your terminal and navigate to the repository. Build the Dockerfile using your Docker Hub username:

docker build -t <YOUR_USERNAME>/stackpath-pwned .
Enter fullscreen mode Exit fullscreen mode

And push the image to Docker Hub:

docker push <YOUR_USERNAME>/stackpath-pwned:latest
Enter fullscreen mode Exit fullscreen mode

Now your image is publicly available. In the next step, you'll deploy the application to the edge using StackPath.

Deploying the Application

Once you’ve created the Node application that calls the Pwned Password API and responds appropriately, you can deploy it to StackPath. StackPath’s edge hosting network runs your Docker containers on servers worldwide, so you get the fastest response times possible.

First, create a new Workload and enter the Docker image you just pushed to Docker Hub above:

You don’t need any environment variables for this project, but you should check the box to “Add Anycast IP Address” and make sure port 80 is open.

Next, choose the PoPs (points of presence) where you want your application to be hosted. I wanted to spread my nodes across the world for benchmarking, but you should select locations according to your use case. I used:

  • Chicago
  • San Jose
  • New York
  • London
  • Singapore

Within a couple of minutes, your StackPath application will be running, and you'll be able to access it from the Anycast IP address shown on your Workload's Overview.

Benchmarking Against Traditional Hosting

Generally, the closer your code is to your users, the faster it will run. To demonstrate the performance benefits that StackPath offers over traditional hosting in the case of pwned password checking, I set up a DigitalOcean droplet in the San Francisco region with similar specs to the StackPath Workload (2GB, 1vCPU).

I used a response timer Docker image to test the application's speed in four locations around the world. The initial uncached response times are pretty similar because in both hosting environments, a request must be made to the Pwned Password API. But, when the result is cached and an API call isn't necessary, StackPath provides significantly faster responses.

On average, responses from StackPath were almost 14x faster than those from a traditional web server.

Conclusions

Passwords are just one piece in the quest for application security, but they’re a cornerstone of implementing strong security practices. By enforcing strong passwords and requiring users to select passwords that haven’t been compromised, you can lower their accounts' risk. To combat any delay in checking passwords, you can utilize edge hosting with StackPath, as demonstrated above.

Top comments (0)