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 is not currently working for me even after the maintainer fixed the issue TypeError: Cannot read property 'url' of undefined.
Cons:
- Not currently working.
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.