Password Protect Static Sites with PageCrypt
February 01, 2022

Password Protect Static Sites with PageCrypt

Chris Castle
Password protecting static sites is tricky. You could use window.prompt() to ask a site visitor to enter a password before the site content is revealed, but a resourceful visitor will quickly get around this with right-click, view source (or curl or numerous other ways). All of the site's HTML, CSS, and JavaScript are served to the web browser, and they constitute the entirety of the site. If you are using a backend API that the static site pulls data from (like many single-page apps do these days), you could require authentication for API requests, but this doesn't protect the HTML, CSS, and JavaScript. In trying to find a good solution for this for Render users, we discovered PageCrypt. It's a free, open source library that allows you to password protect these static assets securely. Let's investigate how PageCrypt works and test it out!

What's it good for?

  • Protecting a static site
  • Cases where you don't need (or can't run) backend code

What are the drawbacks?

  • Only encrypts a single HTML file by default
  • Only supports a single shared password (no per-user passwords)

What is it, and how does it work?

PageCrypt is a novel solution to password protecting HTML without a backend. It’s a library you can use as part of your site’s build step or as a command line tool. It uses the Web Crypto API -- currently supported by all major browsers -- and a password to encrypt an HTML page, which you can then host on any static hosting platform, including Render! An HTML page encrypted with PageCrypt prompts the viewer for a password. Upon entering the correct password, the page is decrypted and its content replaces the password prompt.
Example static site password protected with PageCrypt
One potential concern with PageCrypt is that it only encrypts an HTML file by default. If you want to encrypt your CSS and JavaScript files, you’ll have to inline them in the HTML file. The same applies to images and any other binary assets; you’ll have to inline them as Data URIs. As with any authentication and authorization solution, you’ll want to determine what’s acceptable for your security requirements. Maybe you’re comfortable with the risk of images leaking but have higher security requirements for your JavaScript. In that case, the HTML page can link to the image files but should contain all your JavaScript. You can use many static site build tools to automate inlining assets in HTML. Webpack, Gulp, or Grunt are just a few that might be useful. PageCrypt also doesn't allow users to have individual usernames and passwords. It only works with a single, shared password. If you need the more fine-grained control provided by user accounts, check out a service like Auth0.

Try it out

Adding PageCrypt to the build step of a Hello World static site was straightforward. The instructions in the project’s README provide clear guidance on how to use PageCrypt as a library with browser-executed JavaScript, Node.js, or Deno, and how to use it as a CLI tool. I used the CLI in the build step of my example static site. Add PageCrypt to your project as a dependency: Then update the build step in package.json to use the CLI: The password is set using the $PASSWORD environment variable since we don’t want to store that in the code. Using an environment variable also allows you to change the password and rebuild the site quickly. Here’s an example deployment of the site. The password is s3cr3t. To get a deeper understanding of how PageCrypt works, try entering an incorrect password. Then right-click and view the source of the page. PageCrypt generated the contents of this page during the build step. Your original page content is encrypted inside a hidden <pre> element at the bottom of the HTML document.
Encrypted HTML page inside the <pre> element.
After the correct password is entered, your page content is decrypted and shown. To make it easier for users to access password protected pages, PageCrypt also allows you to create a "magic link" that decrypts the page without prompting the user for a password. The format for the magic link is https://<link-to-your-page>#<password>, placing the password in a URI fragment. Check out the project's README section about magic links to better understand the security implications. When your browser goes to a URL containing a URI fragment, the fragment isn't sent across the internet, but it does remain in the browser's history.

Extend

It would be interesting to extend PageCrypt to do a number of things:
  • Automate the inlining of CSS, JavaScript, and image files
  • Encrypt multiple HTML files
  • Encrypt multiple files, including CSS, JavaScript, and image files
If you do end up extending it in your build process, let me know!

Explore

This version of PageCrypt is a rewrite of an older version of PageCrypt. That older version also inspired a few spin-offs that you might find useful: Now try it out yourself! You can deploy the code to Render for free. The README provides step-by-step instructions.