· engineering  · 4 min read

Basic Authentication for free with Netlify edge functions

How to use Netlify edge functions to password-protect your staging site on the free plan.

How to use Netlify edge functions to password-protect your staging site on the free plan.

This site is hosted on Netlify, initially prompted by my interest in using Decap CMS, which integrates smoothly with Netlify and Astro.
Of course I needed a staging version, but to my surprise, Netlify’s authentication features were limited to paid plans. However, Netlify does offer edge functions…

What Are Edge Functions?

Edge functions allow developers to execute code as traffic enters or leaves your Content Delivery Network (CDN). The functions Netlify offers are similar to AWS CloudFront’s Lambda@Edge, which allows code execution at the edge of the network, but optimized and simplified for specific uses like A/B testing, adding live data, or, in our case, adding Authentication.

Using edge functions, we can implement Basic Access Authentication — a simple, though not very secure, method of authentication. Credentials are sent as base64 encoded strings in every request’s header, making HTTPS essential to protect against middleman attacks. Ensure you have a valid SSL certificate for your staging site. This method suits our staging environment perfectly since it doesn’t require sessions, cookies, or login pages.

For production environments, consider using a more secure authentication method like JWTs (JSON Web Tokens).

Implementing Basic Access Authentication

First, a word of warning on basic authentication: while it is straightforward to implement, it should not be used for any sensitive data as it only provides minimal security and is vulnerable to interception.
That said, it does provide adequate protection to gate off a staging site.

Here’s how you can set up Basic Access Authentication using an edge function saved in “./netlify/edge-functions”, the default location for Netlify to look for edge functions.

./netlify/edge-functions/basic_auth
import type { Context } from '@netlify/edge-functions';
 
/**
 * Workaround for environment variables due to limitations in AWS Lambda@Edge that Netlify is built on.
 */
const credentials = {
  // eslint-disable-next-line no-undef
  Username: Netlify.env.get('BASIC_USERNAME'),
  // eslint-disable-next-line no-undef
  Password: Netlify.env.get('BASIC_PASSWORD'),
};
/**
 * Generate the expected Authorization header value
 * @returns string
 */
const correctAuthString = () => {
  const { Username, Password } = credentials;
  const base64Credentials = btoa(`${Username}:${Password}`);
  return `Basic ${base64Credentials}`;
};
 
/**
 * Handler for the basic auth middleware
 */
export default async (request: Request, context: Context) => {
  const response = await context.next();
 
  const authHeader = request.headers.get('Authorization');
  if (credentials.Password && (!authHeader || authHeader !== correctAuthString())) {
    return new Response('Unauthorized', {
      status: 401,
      headers: {
        'WWW-Authenticate': 'Basic',
      },
    });
  }
  return response;
};

Explanation: The code is pretty simple.
We’re just looking for an authHeader, and checking it matches the expected value.
If its absent, or incorrect, we return a 401 response and a header instructing the browser to request Basic Access Authentication credentials. If the credentials match OR there are no credentials configured, then we return the normal response.

The only slightly unusual aspect is the workaround for environment variables, presumably necessary because AWS Lambda@Edge, which Netlify is built on, does not support them.

Configuring netlify

I started off trying to set my site up as a single site in Netlify, but limitations of the free plan made this rather convoluted.

I have Netlify set up as follows:

  • Two individual sites. one for staging, one for production - this allows me to configure each separately, and enable features (like Identity for DecapCMS just on staging).
  • On the staging site, I have configured “BASIC_USERNAME” and “BASIC_PASSWORD”; on the prod one, I haven’t.
  • Each deploys a different branch of my repository.
  • we add the following section to our netlify.toml. This instructs Netlify to use our edge function for all paths EXCEPT those beginning with .netlify as these are used with Netlify identity services, which, at the time of writing, I was attempting to use for DecapCMS.
    [[edge_functions]]
    path = "/*"
    function = "basic_auth"
    excludedPath="/.netlify/*"

This is enough to have Basic Access Authentication working on staging, and disabled on production.

One last improvement

We do now have Basic Access Authentication on staging and not on production, and so we have a simple way of password protecting our staging site.
However, the edge function is still being executed on every request to production, and there is a (generous) limit to the amount of executions the free plan allows.

Ideally we would remove the edge function entirely for production.
A quick and simple work-around for this is:

  1. create two netlify.toml files: one for staging and one for production.
  2. Adjust the package.json scripts to rename the appropriate configuration file during the build:
    {
      "scripts": {
        "build:staging": "mv ./netlify.toml.staging ./netlify.toml && astro build",
        "build:prod": "mv ./netlify.toml.prod ./netlify.toml && astro build"
      }
    }
  3. On the production site, go to Site Configuration > Build & Deploy > Continuous Deployment and change the value of Build command to npm run build:prod

This setup ensures your staging environment is (somewhat) secure and not indexed by search engines, while keeping production streamlined and free from unnecessary function executions.

James Babington

About James Babington

A cloud architect and engineer with a wealth of experience across AWS, web development, and security, James enjoys writing about the technical challenges and solutions he's encountered, but most of all he loves it when a plan comes together and it all just works.

Comments

No comments yet. Be the first to comment!

Leave a Comment

Check this box if you don't want your comment to be displayed publicly.

Back to Blog

Related Posts

View All Posts »