DEV Community

Jan Hoogeveen
Jan Hoogeveen

Posted on

How to whitelist your Next.js serverless deployments on Now

Originally published on janhoogeveen.eu

I like Next.js. It allows me to build rich data-driven web applications in React that are generated server-side. What I don't like is setting up servers and installing process managers to run node applications for my development and staging servers. To solve this, the clever people at Zeit have developed Now, a serverless platform where you can deploy your web applications with ease. I'd love to use it more, but right now I only see it being a valid choice for production environments.

One simple requirement from my side is that I want to shield my web applications from the public world while I'm still working on it. Unfortunately, that's something Now.sh is not facilitating.

The way Now handles security, is by prefixing your project name with a random unique identifier. For instance, if your project is named my-hidden-project, you can access your deployment on a url like http://my-hidden-project-**kdoe2jfh2**.now.sh

"The UID is derived from a large cryptographically random number encoded as 9 alphanumeric characters. However, if you want additional privacy, you can also replace the .now.sh suffix with any custom domain of your choice, making the URL 100% unguessable, so that not even the very existence of the deployment can be verified by third parties."

This sounds like security through obscurity to me. That's simply not good enough. I want to be 100% certain that I can deploy a web application and that it's only viewable by me or my team members, even if it has a predictable URL.

I've googled around but I could not find a solution to this problem. So, here's my hacky solution to this serious problem which I hope gets solved in a native way soon.

Checking for visitor IP address in your Now Lambdas

When running a Next.js project with a serverless target in the Next.js Builder (@now/next), it passes the request and response objects, or IncomingMessage and ServerResponse as the docs call them to your lambdas. It also passes these arguments to the getInitialProps method, which is always executed on the server before the server returns any DOM representation at all.

Here's a barebone example of disallowing everyone to visit your page.

Example:

const Page = props => {
  return <div>Welcome to next.js!</div>
}
Page.getInitialProps = async ({ req, res }) => {
  res.statusCode = 403
  res.end(`Not allowed`)
}

export default Page

Go ahead and try that out. It's not very useful yet, but it proves you can exit your render early and return a 403 response to the browser. From here, it's a matter of expanding on the example to your liking.

Here's an enhanced version that disallows everyone by default, and whitelists a few IP addresses.

const Page = props => {
  return <div>Welcome to next.js! Your IP address is: {props.ip}</div>
}
Page.getInitialProps = async ({ req, res }) => {
  // Disallow access by default
  let shouldDisallowAccess = true

  // Specify IP's that you want to whitelist. This could be an office/VPN IP address.
  const allowedIps = ['192.168.100.100', '192.168.100.101']

  // Read the request objects headers to find our who's trying to access this page
  var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress

  // This if statement is only valid if the app is deployed through Now. It gets ignored on other environments.
  if (process.env.NOW_REGION) {
    // Check if the visitors IP address is allowed
    if (!allowedIps.includes(ip)) {
      shouldDisallowAccess = false
    }
  }

  // Return the 403 status code
  if (shouldDisallowAccess) {
    res.statusCode = 403
    res.end(`Not allowed for ${ip}`)
    return
  }
  // This object gets injected into the Pages props, if you want to use them
  return { shouldDisallowAccess, ip }
}

export default Page

Alternative solutions

  1. If you don't want to use lambdas, it should be possible to use the Next.js middleware and wrap it in your own server. I haven't tried it yet, but I imagine it would be easy to add basic authentication for disallowd IP addresses with this solution.

  2. If you have control over your own custom domain, it should be possible to point it to Cloudflares DNS servers, and use their firewall to only allow access from specific IP addresses or even IP ranges. That way it should be possible to have a single source of truth of protection - for all projects under that domain - without having to call the getInitialProps method on every page. Of course you lose the benefits of letting Now manage your domain, but do you really need that for your development servers?

Top comments (1)

Collapse
 
peteristhegreat profile image
Peter H

There are a bunch of middleware examples here: github.com/vercel/examples/tree/ma...

Thanks for the great post. Here's how I did it with the _middleware.ts:

import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';

export function middleware(req: NextRequest) {
const allowedIps = ['127.0.0.1', '1.2.3.4'];
const ip = req.headers['x-forwarded-for'] || req.ip;

console.log('ip: ' + ip);
if (ip === undefined) {  // for the dev environment
    return NextResponse.next();
}

if (!allowedIps.includes(ip)) {
    const url = req.nextUrl.clone();
    if (
    url.pathname.startsWith('/favicon') ||
    url.pathname.startsWith('/img')
    ) {
    return NextResponse.next();
    }
    url.pathname = `/coming-soon`;
    const res = NextResponse.rewrite(url);
    return res;
}
return NextResponse.next();
}
Enter fullscreen mode Exit fullscreen mode