DEV Community

loading...

HTTP Basic Auth for Next.js

kkomelin profile image Konstantin Komelin Originally published at komelin.com ・3 min read

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
Enter fullscreen mode Exit fullscreen mode

Create a utility file:

mkdir -p util && touch util/httpAuthCheck.js
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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: {}
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

touch ./pages/_document.js
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

# .env
BASIC_AUTH_CREDENTIALS=user:password
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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"
  },
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (2)

pic
Editor guide
Collapse
kalabro profile image
Kate

Thanks for sharing @kkomelin !

We currently use a custom server approach (via express-basic-auth) on our projects. In future, we want to get rid of a custom server because it breaks some Next.js features like SSG. Looks like Next.js ecosystem doesn't offer a "perfect" solution for it...

Collapse
kkomelin profile image
Konstantin Komelin Author • Edited

You're welcome!
Then in your case I would recommend having a proxy sever on top of your next app which in its turn would provide HTTP auth layer, SSL layer, etc. If your app is containerized, jwilder/nginx-proxy works great for that.