Password Protect Static Sites with PageCrypt
Tags: javascript, webdev, frontend, security
Published: 2022-02-02 20:25:53

---
title: "Password Protect Static Sites with PageCrypt"
published: true
description: Password protecting static sites is tricky. PageCrypt encrypts and password protects HTML pages.
tags: javascript, webdev, frontend, security
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hzir4kqefs8ibcjk7r0.png
canonical_url: https://render.com/blog/static-site-auth-pagecrypt
---

Password protecting static sites is tricky. You could use [`window.prompt()`](https://developer.mozilla.org/en-US/docs/Web/API/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](https://render.com/docs/static-sites)
- 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](https://github.com/Greenheart/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](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) -- currently [supported by all major browsers](https://caniuse.com/cryptography) -- 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.

<figure>
  <video autoplay loop muted playsinline
    poster="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lr4xoplcig7onocq786u.png"
    style="width: 100% !important; height: auto !important; border-radius: 7px;">
    <source src="https://render.com/49b98ee980e86d7ba58e8c7e91cebb2a/pagecrypt-example.mp4" type="video/mp4">
  </video>
  <figcaption>Example static site password protected with PageCrypt</figcaption>
</figure>

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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/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](https://webpack.js.org), [Gulp](https://gulpjs.com), or [Grunt](https://gruntjs.com) 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](https://auth0.com).

## Try it out

Adding PageCrypt to the build step of a *Hello World* static site was straightforward. The instructions in the [project’s README](https://github.com/Greenheart/pagecrypt#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:

```shell
$ yarn add pagecrypt
```

Then update the build step in `package.json` to use the CLI:

```json
{
...<snip>...
  "scripts": {
    "build": "pagecrypt src/index.html dist/index.html $PASSWORD && cp -R src/css dist/"
  },
...<snip>...
}
```

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](https://pagecrypt.onrender.com/) 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.

<figure class="figure" style="width: 90%; margin-left: auto; margin-right: auto;">
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mj0dz6200xsf5g5fy4v.png" alt="A screenshot of a PageCrypt-encrypted HTML page in Google Chrome's elements inspector." />
  <figcaption class="figure__caption">
    Encrypted HTML page inside the &lt;pre&gt; element.
  </figcaption>
</figure>

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](https://en.wikipedia.org/wiki/URI_fragment). Check out the [project's README section about magic links](https://github.com/Greenheart/pagecrypt#share-a-magic-link-to-let-users-open-protected-pages-with-a-single-click) 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](https://twitter.com/crc)!

## Explore

This version of PageCrypt is a rewrite of an [older version of PageCrypt](https://github.com/MaxLaumeister/PageCrypt). That older version also inspired a few spin-offs that you might find useful:

- [Python CLI](https://github.com/MaxLaumeister/pagecrypt/tree/master/python) for PageCrypt
- [PowerShell CLI](https://github.com/MaxLaumeister/pagecrypt/tree/master/PowerShell) for PageCrypt
- [R-wrapper](https://github.com/brentscott93/pagecryptr) for PageCrypt
- [StatiCrypt](https://github.com/robinmoisson/staticrypt) - A separate but similar project

Now try it out yourself! You can deploy the [code](https://github.com/render-examples/pagecrypt) to Render for free. The README provides step-by-step instructions.