HTTP Basic Auth for Next.js
Tags: nextjs, auth, httpbasicauth, basicauth
Published: 2021-04-21 05:23:07

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](https://www.npmjs.com/package/nextjs-basic-auth)

Install the package `nextjs-basic-auth`:

```bash
npm i nextjs-basic-auth
```

Create a utility file:

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

And put the following code into it:

```javascript
// 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:

```javascript
// 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](https://www.npmjs.com/package/nextjs-basic-auth-middleware)

Install nextjs-basic-auth-middleware:

```bash
npm i nextjs-basic-auth-middleware
```

Now let's create `pages/_document.js` to override the [default Document component](https://nextjs.org/docs/advanced-features/custom-document).

```bash
touch ./pages/_document.js
```

Put the following code into it:

```javascript
// 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:

```ini
# .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](https://github.com/labd/nextjs-basic-auth-middleware/issues/2) because it doesn't properly handle the isomorphic nature of Next and may need some adaptation for the client-side rendering.**

**Cons:**
- Not working because of [TypeError: Cannot read property 'url' of undefined](https://github.com/labd/nextjs-basic-auth-middleware/issues/2).

### 3. Custom server with [basic-auth](https://www.npmjs.com/package/basic-auth)

```bash
npm i basic-auth tsscmp
```

Please refer to [the official Next documentation](https://nextjs.org/docs/advanced-features/custom-server) if you're not familiar with the custom server creation.

Create `server.js` file in the project root:

```bash
touch server.js
```

Put the following code into it:

```javascript
// 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:

```json
  "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.

---

Originally published on [my blog](https://komelin.com/articles/http-basic-auth-nextjs/).