HTTP Basic Auth for Next.js

You may need the HTTP basic authentication to protect your secrets/innovations when the app is not ready for production or when your normal authorization mechanism is not yet ready. Let's look at a few possible solutions for the problem in the Next.js context.

Solutions

1. Protect separate routes with nextjs-basic-auth

Install the package nextjs-basic-auth:

npm i nextjs-basic-auth

Create a utility file:

mkdir -p util && touch util/httpAuthCheck.js

And put the following code into it:

// util/httpAuthCheck.js
import initializeBasicAuth from 'nextjs-basic-auth'

const users = [
  { user: 'user', password: 'password' }
]

export default initializeBasicAuth({
  users: users
})

Now you can add the HTTP auth to any of your pages/routes, for example page/index.js.
Open page/index.js and put the following code right after the imports section:

// pages/index.js
import httpAuthCheck from '../util/httpAuthCheck'

export async function getServerSideProps(ctx) {
  const {req, res} = ctx

  await httpAuthCheck(req, res)

  return {
    props: {}
  }
}

To test it, run the app with npm run dev and open http://localhost:3000

Cons:

  • You have to enable the HTTP auth on per page/route basis.

2. (not working currently) Document-level check with nextjs-basic-auth-middleware

Install nextjs-basic-auth-middleware:

npm i nextjs-basic-auth-middleware

Now let's create pages/_document.js to override the default Document component.

touch ./pages/_document.js

Put the following code into it:

// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
import basicAuthMiddleware from 'nextjs-basic-auth-middleware'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const {req, res} = ctx
    await basicAuthMiddleware(req, res)

    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

Then put your auth credentials into your .env file like this:

# .env
BASIC_AUTH_CREDENTIALS=user:password

To test it, run the app with npm run dev and open http://localhost:3000

Unfortunately, this solution causes the TypeError: Cannot read property 'url' of undefined because it doesn't properly handle the isomorphic nature of Next and may need some adaptation for the client-side rendering.

Cons:

3. Custom server with basic-auth

npm i basic-auth tsscmp

Please refer to the official Next documentation if you're not familiar with the custom server creation.

Create server.js file in the project root:

touch server.js

Put the following code into it:

// server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const auth = require('basic-auth')
const compare = require('tsscmp')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  createServer((req, res) => {

    const parsedUrl = parse(req.url, true)
    const credentials = auth(req)

    if (!credentials || !checkHttpAuth(credentials.name, credentials.pass)) {
      res.statusCode = 401
      res.setHeader('WWW-Authenticate', 'Basic realm="example"')
      res.end('Access denied')
    } else {
      handle(req, res, parsedUrl)
    }

  }).listen(3000, (err) => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})

function checkHttpAuth (name, pass) {
  return compare(name, 'user') && compare(pass, 'password');
}

Finally, you need to adapt your package.json to run the custom server:

  "scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  },

As usual, test it with npm run dev and open http://localhost:3000

Cons:

  • With the custom server approach, we will need to track changes in the built-in Next server code and apply them to our server manually with every Next update.

Possible improvements for all code fragments:

  • Extract helper functions to separate files, e.g. checkHttpAuth() function in the custom server solution.
  • Move the user credentials to environment variables.
  • Use TypeScript.

Conclusions

In this post, we have learned three ways to implement HTTP auth in Next.js and looked at two ways to extend a Next.js app, such as overriding the default Document component and creating a custom server.

At the moment, I personally stick with the custom server solution for HTTP auth because I need to protect all routes at once and because it worked for me from the first try.

The solutions I listed here are code-level ones but it's also possible to implement an Nginx proxy which would provide the HTTP auth layer. Please let me know if you're interested in the Nginx solution and I will add it to the list.


I hope you enjoyed the post and learned something new. On this blog, I share my development experience and insights, and if it resonates with you, I invite you to subscribe to my updates.